백엔드 개발자(node.js)가 되는 과정

타입스크립트(typescript) 활용을 위한 기본

soopy 2023. 7. 26. 21:06
728x90

Background

타입스크립트는 Microsoft에서 개발한 오픈 소스 프로그래밍 언어입니다. 이는 JavaScript의 단점을 보안하기 위해 등장했습니다.

자바스크립트의 주요 단점

  • 자바스크립트는 코드가 실행되는 시점에서 변수의 타입이 결정됩니다. 그렇기 때문에 실행하기 전에는 타입으로 인한 오류 확인을 할 수 없었습니다. 이러한 현상이 만약 서비스 중 발생하게 된다면 서버는 곧장 다운될 수 있습니다.
  • 위에서도 언급했지만 타입 체크에 어려움이 있습니다. 특정 프로그래밍 언어에서는 아예 모든 변수 지정 시 타입을 함께 지정하도록 하기도 하지만 자바스크립트는 그런 기능이 없기 때문에 자칫 잘못하면 특정 변수가 언제 Number에서 String타입으로 변경되었는지 파악하는데 오랜 시간이 소요되기도 합니다.
  • 객체 접근에 제한을 걸 수 없습니다. 만약 클래스에서 Contructor로 설정한 init 변수가 외부에서 접근할 수 없도록 만들고 싶다면, 그렇게 할 수 없습니다. 특별한 접근 제한 기능이 없기 때문에 외부에서 얼마든지 클래스 내부 프로퍼티에 접근할 수 있으며 심지어 값을 변경할 수도 있습니다.

타입스크립트의 역할

위 주요 단점을 보완한다고 볼 수 있습니다.

  • 코드 실행 전 에러 메시지를 받아볼 수 있습니다. 즉 코드 입력 단계에서부터 틀리지 않고 잘 작성할 수 있도록 돕는다고 볼 수 있습니다.
  • 생성한 변수의 처음 지정된 타입을 내부에서 기억해 둡니다. 그래서 변수에 할당된 값이 항상 같은 타입을 유지할 수 있도록 돕습니다.
  • 클래스에서 private 기능을 사용하여 프로퍼티의 외부 접근을 방지할 수 있습니다.

타입스크립트에 적용된 컴파일러

타입스크립트 컴파일러인 tsc는 우리가 앞으로 입력할 타입스크립트 코드를 자바스크립트 코드로 변환해주는 역할을 합니다. 그래서 언어 번역을 해주는 기능을 컴파일러 라고 부릅니다.
tsc의 변환 기능이 있어 자바스크립트 코드의 실행 전 언어 변환 과정에서 에러 체크를 하게되므로 정적 타입 검사 수행이 가능해진 것입니다.

타입스크립트 설치 및 설정

이미 node.js가 설치되어 있다는 것을 전제로 합니다.

# 설치
npm i typescript -g

# 타입스크립트 초기화 (tsconfig.json 생성)
tsc --init

# ts파일(index.ts) 컴파일
tsc index.ts

# @types 패키지를 위한 .d.ts파일 생성 명령
tsc index.js --declaration --emitDeclarationOnly

tsconfig.json

타입스크립트 프로젝트의 설정파일입니다.
컴파일러가 es5를 기준으로 번역할지 es2017 기준으로 번역할지 등을 설정할 수 있습니다.
해당 파일에서 compilerOptions - stricttrue로 설정하는 것을 권장하며 개발 단계에서는 compilerOptions - sourceMaptrue로 설정하는 것을 권장합니다.
includeexclude 옵션을 통해 특정 파일 또는 디렉토리를 컴파일에서 포함 또는 제외시킴을 의미합니다.

여기서 compilerOptions를 strict로 설정한다는 것은 엄격한 타입 검사를 수행함을 의미합니다. 가령 잠재적으로 undefined가 될 수 있는 값들에 대해서 검사하거나 함수의 파라미터 또는 변수의 타입이 명시되어 있지 않으면 반드시 명시하도록 경고합니다.

sourceMap 설정은 코드 디버깅에 도움을 주는 옵션으로 코드 실행 시 에러가 발생하면 해당 에러가 타입스크립트 코드 상에서 어느 소스 코드에 해당하는지 알려줍니다.

.d.ts파일은 자바스크립트 라이브러리를 타입스크립트 코드에서 사용 가능하도록 해주는 파일입니다. 구체적으로는 @types 폴더에 존재하며 이 파일들은 자바스크립트의 외부 라이브러리에 대한 타입 정보를 제공하는 역할을 합니다. 그래서 프로젝트에 적용하고자 하는 라이브러리는 그에 해당하는 .d.ts파일이 함께 제공되어야 합니다.

.d.ts 생성 실습

개인 용도로 사용하기 위해 특정 함수를 export하는 js파일을 생성하였고, 이를 사용하기 위해 .d.ts를 생성하는 과정을 실습합니다.

먼저 tsconfig.json 설정을 변경합니다.

"allowJs": true // TypeScript 프로젝트에 JavaScript 파일 허용 여부
"checkJs": true // JavaScript 파일 타입 체크 여부
// 덧셈 함수

/**
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
export function add(a, b) {
  return a + b;
}

JSDoc 주석을 통해 함수의 파라미터와 리턴값의 타입을 알려주고 있는 것을 확인할 수 있습니다. 해당 파일명이 test.js라고 한다면 아래 명령어를 통해 .d.ts를 생성할 수 있습니다.

npx tsc add.js --declaration --allowJs --emitDeclarationOnly --outDir types

이후 types/add.d.ts파일을 확인하면 아래와 같이 생성된 것을 확인할 수 있습니다.

/**
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
export function add(a: number, b: number): number;

그래서 이후 add 함수를 타입스크립트 코드에서도 import해서 사용이 가능해 집니다.

// test.ts
import { add } from './add';
console.log(add(1, 2)); // 3
# 실행 (터미널에 3이 출력됩니다.)
npx ts-node test.ts

 

타입스크립트의 기본 실행 흐름

초기 설정

# 글로벌 설치
npm i typescript -g

# Node 프로젝트 생성
npm init -y

# 타입스크립트 초기화 (tsconfig.json 생성)
tsc --init --rootDir ./src --outDir ./dist --esModuleInterop --module commonjs --strict true --allowJS true --checkJS true

mkdir src

package.json의 script 설정

"scripts": {
    "start": "tsc && node ./dist/index.js",
    "build": "tsc --build",
    "clean": "tsc --build --clean"
},

개발 단계

타입스크립트로 개발 단계에서는 아래와 같은 흐름으로 코드를 테스트 합니다.

-> 타입스크립트로 코드를 작성합니다.
-> tsc --build 명령을 실행하여 타입스크립트 코드를 dist 폴더 안에 js 언어로 컴파일 합니다. (해당과정에서 컴파일 에러를 발견합니다.)
-> tsc && node ./dist/index.js 명령을 통해 생성된 index.js파일을 실행합니다.

위 코드에서는 아래 내용을 전제로 합니다.

  1. 메인 실행파일이 index.js임을 전제로 하고 있습니다.
  2. tsconfig.json에서 "include": ["src/**/*"]
    "exclude": ["./dist"]로 설정이 되어 있습니다.
  3. 컴파일 대상인 ts파일은 src 폴더 내 위치해야 합니다.

 

타입스크립트의 타입 적용법

코드를 통해 타입이 어떻게 적용되는지 직접 눈으로 확인하자

//* 변수에 타입 지정
const name: string = 'apple';
const age: number = 25;

//* 함수 (파라미터: 타입): 리턴 타입
// arrow function
const validName = (name: string): boolean => name.length >= 4;
// 선언형
const validPassword = function (password: string): boolean {
  return password.length >= 8;
};
// 대입형
function validAge(age: number): boolean {
  return age >= 20;
}
// 함수에 리턴값이 없다면 void
const printName = (name: string): void => {
  console.log(name);
};

//* 배열
// 배열(내부 타입이 하나로 고정일 경우
const array: number[] = [1, 2, 3, 4, 5];
const array2: string[] = ['apple', 'ball', 'cat'];
// 배열(내부 타입이 두 개일 경우)
const array3: (string | number)[] = [1, 'apple', 2]; // union이라 부른다.

//* 객체
interface Client {
  name: string;
  age: number;
  role: 'admin' | 'client' // 선택지를 줄 수도 있다.
}
const client: Client = {name: 'apple', age: 99, role: 'admin'}

//* 변수에서의 union
let value: string | number = 3;
value = 'apple'; // 가능

//* 튜플 (튜플은 내부 데이터가 고정되는 것이 원칙이다)
const tuple: [string, number, string] = ['apple', 3, 'ball'];

//* enumerate (User 객체의 프로퍼티를 직접 명시한다.)
// string과 number만 사용 가능
// 클래스처럼 첫글자 대문자로 쓰는 경향
// 상수를 그룹화하여 관리하기 위한 수단으로 주로 사용
// 상수값은 변하지 않는 것이 원칙
enum User {
  USER_KEY = 'apple',
  API_KEY = 28,
  STATUS = 1,
}
// enum의 프로퍼티를 꺼내어 사용하는 느낌이다.
// enum은 상
const key: User = User.USER_KEY;

//* readonly (최초 지정한 값을 변경할 수 없다.)
interface Client {
  readonly name: string;
  readonly age: number;
}
const client: Client = { name: 'apple', age: 18 };
// client.name = 'ball' // 에러 발생

//* any와 unknown
/*
  any는 그냥 쓰면 안된다고 생각하자
  unknown은 타입 명시 전이라는 표시에 사용
*/
const yourname: unknown = 'ball';
console.log(yourname); // ball
// let myname: string = yourname // unknown타입은 string이 아님(오류)
// alias로 임시 타입 지정 가능
let myname: string = yourname as string;

//* object literal ( 자바스크립트 객체랑 뭐가 다르지?? )
// obj.b -> [1, 2, 3]이 출력되어 그냥 쓰면 된다.
const obj = {
  a: 1,
  b: [1, 2, 3],
  c: 'apple',
};

// -- 유틸리티 타입
//* Partial<T>
// interface 내 프로퍼티 중 일부만 사용 가능하도록 하는 기능
// 아래처럼 프로퍼티 업데이트에 사용할 때 주로 쓰이는 듯 하다
interface Todo {
  title: string;
  description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
  title: 'organize desk',
  description: 'clear clutter',
};
const todo2 = updateTodo(todo1, {
  description: 'throw out trash',
});

//* Required<T>
interface Client {
  name: string;
  age: number;
  address?: string; // '?'는 있어도 없어도 된다는 뜻
}
type RequiredClient = Required<Client>;
const client: Client = { name: 'apple', age: 999 }; // 가능
// 아래 코드는 address도 입력 필수
// const requiredClient: RequiredClient = { name: 'apple', age: 999 };

//* Readonly<T>
interface Client {
  name: string;
  age: number;
}

const immutableClient: Readonly<Client> = {
  name: 'apple',
  age: 990,
};
// immutableClient.name = 'ball' // 재할당 안됨(읽기 전용)

//* Pick<T, K>
// 타입 T에서 선택한 K 프로퍼티만 사용 가능
interface Client {
  name: string;
  age: number;
  address: string;
}

type pickedClient = Pick<Client, 'name' | 'age'>;
// const client: pickedClient = {name: 'cat', age: 4, address: 'aaa'} // address는 입력 불가

//* Omit<T, K>
// 타입 T에서 선택한 K 프로퍼티는 사용 불가
interface Client {
  name: string;
  age: number;
  address: string;
}

type omittedClient = Omit<Client, 'name' | 'age'>;
// const client: omittedClient = {name: 'cat', age: 4, address: 'aaa'} // name과 age는 입력 불가

위에서 소개한 유틸리티 타입 외 타입은 아래 링크 참조
유틸리티 타입

728x90
728x90