[TypeScript] What’s new 4.0 (simply)

Dongmin Jang
10 min readSep 10, 2020

--

Vue(Nuxt) + Typescript 조합으로 새로운 프로젝트를 준비하던 중에 typescript 4.0이 배포되어 정리해 봅니다.

Variadic Tuple Types

첫번째 변화는 tuple type 에서의 spread가 generic이 될 수 있다는 것이다. 이 말은 우리가 실제 type을 모른다 하더라도 tuple, array에서 higher-order operations으로 표현할 수 있다는 것이다. generic spreads가 이러한 tuple type에서 인스턴스화 되면(또는 실제타입으로 대체되면), 또다른 array 나 tuple을 만들어 낼 것이다.

function tail<T extends any[]>(arr: readonly [any, ...T]) {
const [_ignored, ...rest] = arr;
return rest;
}
const myTuple = [1, 2, 3, 4] as const;
const myArray = ["hello", "world"];
const r1 = tail(myTuple);
// ^ = const r1: [2, 3, 4]
const r2 = tail([...myTuple, ...myArray] as const);
// ^ = const r2: [2, 3, 4, ...string[]]

두번째 변화는 rest element가 tuple 어디에도 위치할 수 있다 — 끝에 두지 않아도 된다는 것!

type Strings = [string, string];
type Numbers = number[];
type StrStrNumNumBool = [...Strings, ...Numbers, boolean];
// ^ = type StrStrNumNumBool
= [string, string, ...(number | boolean)[]

위 두 특징을 조합해서 type을 잘 정리한 concat 을 만들 수 있습니다.
(override를 할 필요가 없어졌네요)

type Arr = readonly any[];function concat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] {
return [...arr1, ...arr2];
}

rest paramers 나 rest tuple 에 대한 추론 기능도 향상되서 아래와 같이 할 수 도 있습니다.

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);
}

위와 같이 코드를 작성하면 어떤 paremeter가 초기화 될 수 있는지, return function이 정확하게 실행 여부를 판단 할 수 있습니다.

(partialCall에 대해, 위에서는 paremer가 어떻게 위치되는지를 명시해줬다면, 아래 코드에서 parameter의 타입 형식을 정확히 잡아줌)

const foo = (x: string, y: number, z: boolean) => {};const f1 = partialCall(foo, 100);// number가 아닌 string이 두번째 인자로 와야 합니다const f2 = partialCall(foo, "hello", 100, true, "oops");// 인자가 한개 더 들어왔네요// This works!
const f3 = partialCall(foo, "hello");
// ^ = const f3: (y: number, z: boolean) => void
// What can we do with f3 now?// Works!
f3(123, true);
f3();
// 인자가 안들어옴
f3(123, "hello");
// strin이 아닌 boolean이 들어와야 함

Labeled Tuple Elements

tuple에 label을 붙이면 가독성이 좋아집니다.

function foo(...args: [string, number]): void {   // ... }// 아래와 같이 표현 할 수도 있습니다.
function foo(arg0: string, arg1: number): void { // ... }
foo("hello", 42);foo("hello", 42, true);
// 인자가 2개만 들어와야 되는데 3개나 들어왔네요
foo("hello");
// 인자가 2개 들어와야 되는데 1개만 들어왔네요
// 가독성!
type Range = [start: number, end: number];
type Foo = [first: number, second?: string, ...rest: any[]];type Bar = [first: string, number];
// 모두 이름을 붙이던가 아니면 안붙이던가 통일해야 합니다
function foo(x: [first: string, second: number]) {
// ...
// 변수에 tuple label을 사용할 필요는 없습니다.
const [a, b] = x;
a
// ^ = const a: string
b
// ^ = const b: number
}
type Name =
| [first: string, last: string]
| [first: string, middle: string, last:string]
function createPerson(...name: Name) {
// ...
}
createPerson() <== IDE(vs code)에서 guide text가 나옵니다

Class Property Inference from Constructors

class의 member 변수는 생성자를 추론해서 변수를 할당되거나 undefiend가 암묵적으로 들어가게 됩니다.

그런데 아래와 같이 annotation을 이용해서 type을 명시할 수 있습니다.

class Square {
// definite assignment assertion
// v
sideLength!: number;
// type annotation
constructor(sideLength: number) {
this.initialize(sideLength);
}
initialize(sideLength: number) {
this.sideLength = sideLength;
}
get area() {
return this.sideLength ** 2;
}
}

Short-Circuiting Assignment Operators

logical and (&&), logical or (||), and nullish coalescing (??).

&&=, ||=, and ??=.

// could be 'a ||= b'
if (!a) {
a = b;
}
let values: string[];
(values ?? (values = [])).push("hello");

// After
(values ??= []).push("hello");
obj.prop ||= foo();// 아래 둘과 비슷obj.prop || (obj.prop = foo());if (!obj.prop) {
obj.prop = foo();
}
const obj = {
get prop() {
console.log("getter has run");
// Replace me!
return Math.random() < 0.5;
},
set prop(_val: boolean) {
console.log("setter has run");
}
};
function foo() {
console.log("right side evaluated"); return true;}console.log("항상 setter를 실행");
obj.prop = obj.prop || foo();
console.log("이건 항상 실행되지 않음");
obj.prop ||= foo();

Unknown on catch Clause Bindings

catch의 변수에 기본적으로 any가 type으로 지정되서 코드를 마음대로 작성할 수 있는데, 이렇게 되면 에러 핸들링에 대한 코드에서 에러가 발생할 수 있다. 그래서 unknown을 이용해서 type을 확인하도록 상기시켜주는 것이 좋다.

try {  // ...} catch (e: unknown) {  // unkown에 바로 접근 못함  console.log(e.toUpperCase());  // Object is of type 'unknown'.  if (typeof e === "string") {    // We've narrowed 'e' down to the type 'string'.    console.log(e.toUpperCase());  }}

Custom JSX Factories

{
compilerOptions: {
target: "esnext",
module: "commonjs",
jsx: "react",
jsxFactory: "h",
jsxFragmentFactory: "Fragment",
},
}
// Note: these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from "preact";export const Header = ( <> <h1>Welcome</h1> </>);…아래 코드로 나올거에요// Note: these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from "preact";
export const Header = (h(Fragment, null,
h("h1", null, "Welcome")));

Speed Improvements in build mode with --noEmitOnError

--noEmitOnError flag를 사용할 때, 컴파일이 한번 진행 된 후(—- incremental error)의 다음 컴파일이 엄청 느렸는데, 이게 개선되었습니다.

-— noEmitOnError 때문에 이전 컴파일의 빈 정보가 .tsbuildinfo 파일이 캐시 되어서 그런 문제라고 합니다.

— build mode scenarios (--incremental and --noEmitOnError)

--incremental with --noEmit

-— incremental 컴파일이 될 때에도 —- noEmit flag를 사용할 수 있다고 합니다. 이전에는 —- incremental flag가 .tsbuildinfo 파일에 eimit을 해야 했는데, 속도때문에 바뀐거 같습니다.

Editor Improvements

Convert to Optional Chaining

vs code에서 popup메뉴로 손쉽게 할 수 있게 되었습니다.

/** @deprecated */ Support

deprecated 주석을 사용하면 vs code 에서 popup으로 guide 문국가 노출 됩니다.

Smarter Auto-Imports

@type 을 이용해서 vs code에서 손쉽게 ts type을 사용할 수 있게 되었습니다.

--

--

No responses yet