Typescript 4.5의 새 기능

Typescript

Posted by jopemachine on November 19, 2021 Updated on September 25, 2022

Typescript 4.5의 새 기능

  • Typescript 4.5에서 추가된 기능들을 공부하며 정리해보았다. (모든 추가 사항을 정리한 것은 아님.)

새 유틸리티 타입, Awaited

1
2
3
4
5
6
7
8
// A = string
type A = Awaited<Promise<string>>;

// B = number
type B = Awaited<Promise<Promise<number>>>;

// C = boolean | number
type C = Awaited<boolean | Promise<number>>;

타입스크립트 4.5에 Awaited라는 유틸리티 타입이 추가되었다.

Awaited는 재귀적으로 프라미스일 수 있는 값들을 Unwrap 한 타입을 만든다.

예를 들어 기존의 타입스크립트에선

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
declare function MaybePromise<T>(value: T): T | Promise<T> | PromiseLike<T>;

async function doSomething(): Promise<[number, number]> {
    const result = await Promise.all([
        MaybePromise(100),
        MaybePromise(200)
    ]);

    // Error!
    //
    //    [number | Promise<100>, number | Promise<200>]
    //
    // is not assignable to type
    //
    //    [number, number]
    return result;
}

에러 메시지에 나오듯이 위 함수 doSomething[number | Promise<100>, number | Promise<200>][number, number]에 대입할 수 없다는 컴파일 에러가 난다.

기존의 타입스크립트로 doSomething을 작성하기 위해선, 아래처럼 리턴 타입을 복잡하게 명시해야 한다.

1
2
3
async function doSomething(): Promise<[number | Promise<number>, number | Promise<number>]> {
  ...
}

Awaited를 사용하면, 아래 함수를 보다 직관적으로 정의할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
declare function MaybePromise<T>(value: T): T | Promise<T> | PromiseLike<T>;

async function doSomething(): Awaited<Promise<[number, number]>> {
    const result = await Promise.all([
        MaybePromise(100),
        MaybePromise(200)
    ]);

    // OK
    return result;
}

node_modules에서의 lib 지원

Template string을 통한 타입 판별 (Discriminants) 지원

기존의 타입스크립트에서 다음 코드는 아래와 같은 컴파일 에러가 난다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export interface Success {
  type: `${string}Success`;
  body: string;
}

export interface Error {
  type: `${string}Error`;
  message: string;
}

export function handler(r: Success | Error) {
  if (r.type === "HttpSuccess") {
      // Property 'body' does not exist on type 'Success | Error'.
      let token = r.body;
  }
}

타입스크립트 4.5 이후부터는 r.typeSuccess로 끝나는 string 이라는 것을 검사한 이후엔, 자동으로 Success 타입으로 추론되어, 컴파일 에러가 없어진다.

Conditional Types의 꼬리 재귀 제거

import 문 생략 비활성화

기존의 타입스크립트에서 아래 코드를 컴파일 하면, Animal을 사용하고 있는 변수로 탐지하지 못해, 해당 import 구문을 제거해, 개발자의 의도대로 동작하지 않는다.

1
2
3
import { Animal } from "./animal.js";

eval("console.log(new Animal().isDangerous())");

4.5 이후엔 preserveValueImports 플래그를 사용해 타입스크립트의 이런 동작을 변경할 수 있다.

타입 import 구문 확장 (type Modifiers on Import Names)

아래 코드는 기존 타입스크립트에서 에러를 낸다.

BaseType은 타입이고, someFunc은 함수여서 ts-loader 등의 라이브러리들은 컴파일 타임에 어떤 value가 남겨져야 하는지 알 수 없어 에러를 낸다.

1
2
3
4
5
6
// Which of these is a value that should be preserved? tsc knows, but `ts.transpileModule`,
// ts-loader, esbuild, etc. don't, so `isolatedModules` issues an error.
import { someFunc, BaseType } from "./some-module.js";
//                 ^^^^^^^^
// Error: 'BaseType' is a type and must be imported using a type-only import
// when 'preserveValueImports' and 'isolatedModules' are both enabled.```

위 statement는 기존의 타입스크립트에서 아래처럼 "./some-module.js"를 중복해 써야 했다.

1
2
import type { BaseType } from "./some-module.js";
import { someFunc } from "./some-module.js";

4.5 이후에선 아래처럼 한 번에 가져올 수 있다.

1
2
3
4
5
6
7
import { someFunc, type BaseType } from "./some-module.js";

export class Thing implements BaseType {
    someMethod() {
        someFunc();
    }
}

위처럼 한 번에 가져와도 알아서 BaseType은 컴파일 타임에 지워지고, someFunc은 남는 것이 보장된다.

예를 들어 위 코드는 아래처럼 컴파일 된다.

1
2
3
4
5
6
7
import { someFunc } from "./some-module.js";

export class Thing {
    someMethod() {
        someFunc();
    }
}

private 변수 존재 체크

타입스크립트 4.5 이후부턴 해당 객체의 private 변수가 존재하는지 in operator를 사용해 확인할 수 있다.

예를 들어 아래와 같은 코드를 작성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
    #name: string;
    constructor(name: string) {
        this.#name = name;
    }

    equals(other: unknown) {
        return other &&
            typeof other === "object" &&
            #name in other && // <- this is new!
            this.#name === other.#name;
    }
}

import 구문의 타입 assertions

타입스크립트 4.5에선, import 구문에서 가져온 값의 타입을 명시할 수 있다.

이것은 런타임에 import 구문이 해당 타입을 import 하는 것을 확실히 해 준다.

예를 들어 아래와 같은 코드 작성이 가능하다.

1
import obj from "./something.json" assert { type: "json" };
1
2
3
import obj from "./something.json" assert {
    type: "fluffy bunny"
};

Dynamic import 구문 같은 경우 아래처럼 인자로 넘기는 식으로 쓸 수 있다.

1
2
3
const obj = await import("./something.json", {
    assert: { type: "json" }
})

원문