TypeScript 4.0을 정리해보자.

mirrous
8 min readAug 30, 2020

--

얼마 전, TypeScript 4.0이 정식으로 릴리즈되었습니다(공식 블로그). 개인적으로는 3.7을 업데이트할 때 만큼의 급격한 변화를 느끼진 못했지만 글을 읽다 보니 도입하면 좋을만한 여러 문법들을 찾을 수 있었습니다.

이번 업데이트로 추가, 변경된 내용들을 정리하면서 다른 사람들에게 공유하면 도움이 될 것 같아서 부족하지만 번역 겸 포스트 작성을 시작했습니다. 코드 변화는 간단한 예시와 함께, 그 외에는 간단한 설명으로 정리해보겠습니다.

원문이나 업데이트 정보와 다른 부분에 대한 피드백은 언제나 환영합니다!

가변 튜플 타입

먼저 튜플 타입이란 간단한 데이터 구조로 여러 요소들을 그룹화하는 방법입니다.

const [foo, setFoo] = useState("bar");

Hooks 기반의 React를 사용하면서 볼 수 있는 대표적인 튜플 타입입니다. 얼핏 보면 배열처럼 묶여있지만 서로 관련 있는 다른 타입을 모아놓은 타입을 튜플 타입이라고 부릅니다.

업데이트 이전에는 ... 연산자를 사용하여 튜플 타입을 확장하려하면 컴파일러에서 오류를 발생시켰습니다.

type StringsTuple = [string, string];type ExtendedStringsTuple = [...StringsTuple, string];
// 에러! 전개 연산자는 객체나 배열 타입에서만 사용할 수 있습니다.

업데이트 이후에는 튜플에 전개 연산자를 사용하여 새로운 타입을 정의할 수 있게 되었습니다

type NumbersTuple = [number, number];type ExtendedNumbersTuple = [...NumbersTuple, number];
// 통과! [number, number, number]로 타입이 정의됩니다.

이런 가변 튜플 형식을 사용한 예제를 살펴보겠습니다. 아래 함수(partialCall)는 매개변수로 들어온 함수(f)의 매개변수를 일부 적용한 다른 함수를 리턴하는 함수입니다.

type Arr = readonly unknown[];function partialCall<T extends Arr, U extends Arr, R>(
f: (...args: [...T, ...U]) => R,
...headArgs: T
) {
return (...tailArgs: U) => f(...headArgs, ...tailArgs);
}

이 함수는 이렇게 사용됩니다.

const foo = (x: string, y: number, z: boolean) => {};const f1 = partialCall(foo, 100);
// 에러! 첫번째 매개변수의 타입은 string만 받을 수 있으므로 부분함수를 생성할 수 없습니다.
const f2 = partialCall(foo, "hello", 100, true, "oops");
// 에러! foo의 매개변수는 3개이지만 string 타입("oops")을 하나 더 받고 있습니다.
const f3 = partialCall(foo, "hello");
// 통과! x에 "hello"가 들어간 foo를 호출해주는 함수가 생성되었습니다.
f3(30, true);
// 통과! foo("hello", 30, true)가 호출된 것과 동일하게 동작됩니다.
f3(30);
// 에러! boolean 타입의 매개변수가 부족하여 에러가 됩니다.

위의 예시처럼 매개변수가 많거나 알 수 없는 함수를 사용하더라도 컴파일러 단계에서 타입 추론을 할 수 있도록 가변 튜플 타입을 활용할 수 있습니다.

명명된 튜플 요소

튜플 타입을 함수의 파라미터로 전개해서 사용하면 아래처럼 표현됩니다.

function foo(...args: [string, number]): void { }
// 이 함수는 컴파일러에서 아래와 같이 읽습니다.
function foo(args_0: string, args_1: number): void { }

튜플 타입을 전개하여 파라미터로 넣어주면 튜플 요소의 이름이 지정되지 않아서 가독성이 떨어지는 문제가 있었습니다. 이 문제를 해결하기 위해 이번 업데이트에서는 명명된 튜플 요소를 추가했습니다. 사용 방법은 아래와 같습니다.

type CustomTuple = [first: string, second: number];

이 튜플을 위처럼 함수로 만들었을 때 이렇게 표현됩니다.

function bar(...args: CustomTuple): void { }
// 이 함수는 컴파일러에서 아래와 같이 읽습니다.
function bar(first: string, second: number): void {}

함수의 파라미터에 이름이 생겨서 기존보다 가독성이 향상된 것을 보실 수 있습니다. 다만, 명명된 튜플 요소를 사용하기 위한 제약조건은 모든 요소에 이름을 지정해주어야 합니다.

type CustomTuple = [first: string, number];
// 에러! 튜플의 요소는 모두 명명되어 있거나 이름이 없어야 합니다.

생성자로부터 클래스 멤버 변수의 타입을 추론

이전까지는 멤버 변수에 타입을 지정해주지 않으면 any로 타입을 추론했습니다. 이번 업데이트부터 생성자로부터 타입을 추론할 수 있도록 변경됩니다!

class Square {
// 업데이트 전: any로 추론
// 업데이트 후: 'number'로 추론
area;
sideLength;
constructor(sideLength: number) {
this.sideLength = sideLength;
this.area = sideLength ** 2;
}
}

거기에 아래의 코드 블럭에서 볼 수 있듯이 멤버 변수가 분기로 인해 대입되지 않는 상황도 같이 고려해 줍니다.

class Square {
sideLength;

consturctor(sideLength: number) {
if (Math.random()) {
this.sideLength = sideLength;
}
}
get area() {
return this.sideLength ** 2;
// 에러! sideLength가 undefined일 수 있습니다.
}
}

다만 생성자에서 다른 함수를 호출하여 값을 대입해주면 any 로 추론하기 때문에 이런 경우에는 타입 선언과 함께 확정적 할당 선언(!)을 추가해줘야 에러를 피할 수 있습니다.

class Square {
sideLength!: number;
constructor(sideLength: number) {
this.initialize(sideLength);
}
initialize(sideLength: number) {
this.sideLength = sideLength;
}
get area() {
return this.sideLength ** 2;
}
}

단락 할당 연산자 추가

할당 연산자는 = 부터 시작하여, +=, -=, *=, /= 처럼 복합적인 할당 연산자가 존재합니다.

이번 업데이트부터 단락 연산자(&&, ||, ??)들에도 할당 연산자가 추가되었습니다.

a &&= b;
a ||= b;
a ??= b;

a ??= b;의 경우 TypeScript 3.7부터 추가된 nullish coalescing을 할당 연산자로 사용할 수 있게 되었습니다. 예시는 다음과 같습니다.

let values: string[];
(values ?? (values = [])).push("hello");
// 업데이트 전
(values ??= []).push("hello");
// 업데이트 후

catch 구문으로 넘어온 예외를 unknown Binding

기존에는 catch 구문으로 넘어온 예외는 모두 any로 취급되어 실제로 없는 변수, 메서드도 모두 사용 가능한 것처럼 보이는 문제가 있었습니다.

try {
// ...
} catch (e) {
// 마음껏 즐기세요! e가 any로 추론되어 에러가 발생하지 않습니다.
console.log(e.message);
console.log(e.toUpperCase());
e++;
e.foo().bar().baz();
}

이런 문제를 회피하기 위해 타입을 지정해주고 싶어도 지정해줄 수 없는 문제가 있었습니다.

이번 업데이트부터 e를 unknown으로 바인딩 할 수 있게 해줌으로써 타입 체킹을 좀 더 수월하게 할 수 있게 되었습니다.

try {
// ...
} catch (e: unknown) {
// 에러! unknown에는 toUpperCase 프로퍼티가 존재하지 않습니다.
console.log(e.toUpperCase());
if (typeof e === "string") {
// 통과!
console.log(e.toUpperCase()
}
}

TypeScript 4.0에서의 코드 측면 변경점은 모두 설명이 끝났습니다. 이 외에도 JSX 옵션 변경이나 빌드 측면에서의 성능 향상과 같은 다양한 변경점이 있는데, 이 부분은 다음 포스트나 여기에 추가적으로 올리도록 하겠습니다.

감사합니다!

--

--

No responses yet