DEV Community

Cover image for JavaScript 프로그래머를 위한 TypeScript
Jinhyun Kim
Jinhyun Kim

Posted on

JavaScript 프로그래머를 위한 TypeScript

이 포스트는 TypeScript for JavaScript Programmers를 학습한 내용입니다.

   

TypeScript(TS)는 JavaScript(JS)의 모든 기능을 제공한다. 즉, 모든 JS 코드는 TS 코드이다. 여기에 TS는 타입 시스템을 추가로 제공한다.

JS에서는 string, number, object 등 다양한 값의 타입이 존재하지만, 변수에 할당된 값의 타입이 유지되는지 체크하지 않는다. 즉, 같은 변수에 다른 타입의 값을 재할당 할 수 있다.

let str = "cheese";
str = true;
Enter fullscreen mode Exit fullscreen mode

   

반면, TS에서는 타입 추론을 통해 변수에 할당된 값의 타입으로 타입을 결정한다. 이후 값을 재할당하면 재할당된 값의 타입이 이전에 결정된 타입과 같은지 체크한다.

let str = "cheese";
str = true;
// Type 'boolean' is not assignable to type 'string'.
Enter fullscreen mode Exit fullscreen mode

TS의 가장 큰 장점은 의도하지 않은 코드 실행을 런타임 이전에 방지하여 버그 발생의 가능성을 줄일 수 있다는 것이다.

  

1. 타입 추론 (Types by Inference)

TS를 사용하여 변수를 선언 후 값을 할당하면, 할당된 값의 타입을 타입으로 사용한다. 타입 추론을 통해 타입이 결정되므로 명시적으로 타입을 작성하지 않아도된다.

let helloWorld = "Hello World";
// let helloWorld: string = 'Hello World';
Enter fullscreen mode Exit fullscreen mode

  

2. 타입 정의 (Defining Types)

JS 프로그래밍은 다양한 디자인 패턴을 통해 이루어진다. 이때 동적 프로그래밍을 사용하는 패턴은 타입 추론을 하기 어렵게 만든다. 이러한 경우 TS의 interface, type 기능을 사용할 수 있다.

예를 들어 문자열 타입의 name, 숫자 타입의 id 프로퍼티를 가진 객체는 아래와 같이 생성할 수 있다.

const user = {
  name: "Jin",
  id: 0,
};
Enter fullscreen mode Exit fullscreen mode

interface를 선언하여 위 객체의 구조를 명시적으로 작성할 수 있다.

interface User {
  name: string;
  id: number;
}
Enter fullscreen mode Exit fullscreen mode

위에서 선언한 User 인터페이스를 따르는 객체를 생성하기 위해서는 : TypeName 문법을 사용한다.

const user: User = {
  name: "Jin",
  id: 0,
};
Enter fullscreen mode Exit fullscreen mode

: TypeName에 작성한 타입과 다른 타입의 값을 할당하면 에러가 발생한다.

interface User {
  name: string;
  id: number;
}

const user: User = {
  username: "Jin",
  id: 0,
};

// Type '{ username: string; id: number; }' is not assignable to type 'User'.
// Object literal may only specify known properties, and 'username' does not exist in type 'User'.
Enter fullscreen mode Exit fullscreen mode

  

클래스로 생성한 인스턴스와 인터페이스를 함께 사용할 수도 있다.

interface User {
  name: string;
  id: number;
}

class UserAccount {
  name: string;
  id: number;

  constructor(name: string, id: number) {
    this.name = name;
    this.id = id;
  }
}

const user: User = new UserAccount("Jin", 0);
Enter fullscreen mode Exit fullscreen mode

  

인터페이스를 사용하여 함수의 매개변수와 리턴 값의 타입도 결정할 수 있다.

function getAdminUser(): User {
  // ...
}

function deleteUser(user: User) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

JS에서 기본적으로 제공하는 값의 타입(boolean, bigint, null, number, string, symbol, object, undefined)을 인터페이스에서 그대로 사용할 수 있다. 여기에 TS는 any, unknown, never, void 타입을 추가로 제공한다.

  

3. 타입 구성 (Composing Types)

Union, Generic을 사용하여 여러 타입을 하나의 타입으로 합칠 수 있다.

Unions

Union을 사용하여 타입을 선언할 수 있다. 예를 들어 true 혹은 false 타입의 값을 가지는 MyBool 타입을 아래와 같이 생성할 수 있다.

type MyBool = true | false;
// type MyBool = boolean
Enter fullscreen mode Exit fullscreen mode

위 예제의 MyBool 타입은 구조적 타입 시스템(structural type system)에 의해 boolean 타입으로 분류된다.

  

Union 타입은 유효한 리터럴 목록을 구조화할 때 사용할 수 있다.

type WindowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type OddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;
Enter fullscreen mode Exit fullscreen mode

  

Union을 사용하면 리터럴 뿐만 아니라 여러 타입을 하나로 묶을 수 있다. 예를 들어, 매개변수의 타입이 array 혹은 string인 함수를 아래와 같이 작성할 수 있다.

function getLength(obj: string | string[]) {
  return obj.length;
}
Enter fullscreen mode Exit fullscreen mode

   

변수의 타입을 확인하기 위해서는 typeof 연산자를 사용한다.

Type Predicate
string typeof s === "string"
number typeof n === "number"
boolean typeof b === "boolean"
undefined typeof undefined === "undefined"
function typeof f === "function"
array Array.isArray(a)

  

예를 들어 매개변수의 타입에 따라 리턴 값이 달라지는 경우 아래와 같이 함수를 작성할 수 있다.

function wrapInArray(obj: string | string[]) {
  if (typeof obj === "string") {
    return [obj];
  } else {
    return obj;
  }
}
Enter fullscreen mode Exit fullscreen mode

  

Generics

제네릭은 타입을 변수 형태로 전달하여 사용하는 방법이다. 예를 들어 일반 배열은 모든 타입의 값을 포함할 수 있다. 하지만 제네릭 배열은 전달된 타입 변수와 자신의 요소 타입이 일치해야하며, 이를 통해 자신의 요소가 무슨 타입인지 표현할 수 있다.

type StringArray = Array<string>; // ["foo", "bar", "baz"]
type NumberArray = Array<number>; // [1, 2, 3, 4]
type ObjectWithNameArray= Array<{ name: string }>; // [{ name: "Jin" }, { name: "Joon" }]
Enter fullscreen mode Exit fullscreen mode

  

제네릭을 사용하는 커스텀 타입을 직접 선언할 수 있다.

interface BackPack<Type> {
  add: (stuff: Type) => void;
  get: () => Type;
}

declare const backpack: Backpack<string>;

// Backpack의 Type 자리에 string을 전달하였으므로 아래 stuff는 문자열 타입이다.
const stuff = backpack.get();

// Backpack의 Type 자리에 string을 전달하였으므로 숫자 타입은 add 할 수 없다.
backpack.add(23);
// Argument of type 'number' is not assignable to parameter of type 'string'.
Enter fullscreen mode Exit fullscreen mode

  

4. 구조적 타입 시스템 (Structural Type System)

TS의 핵심 개념 중 하나는 타입 체크가 값의 구조(shape)에 의해 이루어진다는 것이다. 이러한 개념을 "duck typing" 혹은 "structural typing"이라 한다.

만약 두 객체가 같은 구조를 가지고 있다면, 이 둘은 구조적 타입 시스템에 의해 같은 타입으로 인식된다.

interface Point {
  x: number;
  y: number;
}

function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

const point = { x: 12, y: 26 };
logPoint(point); // 12, 26
Enter fullscreen mode Exit fullscreen mode

위 예제의 point 변수는 Point 타입으로 선언되지 않았다. 하지만 TS는 타입 체크 시 point의 구조와 Point 구조를 비교하기 때문에 위 코드는 문제 없이 컴파일된다.

  

구조가 일치하는지 판단하는 기준은 프로퍼티 수가 더 적은 객체의 구조를 프로퍼티 수가 더 많은 객체가 충족하는지이다.

const point2 = { x: 15, y: 13: z: 89 };
logPoint(point2); // 15, 13

const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect); // 33, 3

const color = { hex: "#187ABF" };
logPoint(color);
// Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'.
// Type '{ hex: string; }' is missing the following properties from type 'Point': x, y
Enter fullscreen mode Exit fullscreen mode

   

이러한 규칙은 class를 사용할 때에도 동일하게 적용된다.

class VirtualPoint {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

const newVPoint = new VirtualPoint(13, 56);
logPoint(newVPoint); // 13, 56
Enter fullscreen mode Exit fullscreen mode

  

출처

Top comments (0)