JavaScript 프로그래머를 위한 TypeScript
Tags: javascript, typescript
Published: 2021-01-31 15:26:46

> 이 포스트는 [TypeScript for JavaScript Programmers](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html)를 학습한 내용입니다.

&nbsp;  

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

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

```js
let str = "cheese";
str = true;
```

&nbsp;  

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

```typescript
let str = "cheese";
str = true;
// Type 'boolean' is not assignable to type 'string'.
```

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

&nbsp;  

## 1. 타입 추론 (Types by Inference)

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

```typescript
let helloWorld = "Hello World";
// let helloWorld: string = 'Hello World';
```

&nbsp;  

## 2. 타입 정의 (Defining Types)

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

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

```typescript
const user = {
  name: "Jin",
  id: 0,
};
```

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

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

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

```typescript
const user: User = {
  name: "Jin",
  id: 0,
};
```

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

```typescript
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'.
```

&nbsp;  

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

```typescript
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);
```

&nbsp;  

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

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

function deleteUser(user: User) {
  // ...
}
```

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

&nbsp;  

## 3. 타입 구성 (Composing Types)

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

### Unions

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

```typescript
type MyBool = true | false;
// type MyBool = boolean
```

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

&nbsp;  

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

```typescript
type WindowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type OddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;
```

&nbsp;  

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

```typescript
function getLength(obj: string | string[]) {
  return obj.length;
}
```

&nbsp;  

변수의 타입을 확인하기 위해서는 `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)                 |

&nbsp;  

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

```typescript
function wrapInArray(obj: string | string[]) {
  if (typeof obj === "string") {
    return [obj];
  } else {
    return obj;
  }
}
```

&nbsp;  

### Generics

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

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

&nbsp;  

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

```typescript
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'.
```

&nbsp;  

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

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

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

```typescript
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
```

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

&nbsp;  

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

```typescript
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
```

&nbsp;  

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

```typescript
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
```

&nbsp;  

## 출처

* [TypeScript for JavaScript Programmers](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html)