얼마 전, 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 옵션 변경이나 빌드 측면에서의 성능 향상과 같은 다양한 변경점이 있는데, 이 부분은 다음 포스트나 여기에 추가적으로 올리도록 하겠습니다.
감사합니다!