티스토리 뷰

카테고리 없음

typescript 3.7 소개

박스여우 2019. 12. 4. 02:11

최근 이직을 한 후 개인적으로 '공부' 라는 것과 거리가 멀어져서 포스팅을 쓰면서 다시 시작해보고자 합니다. 마침 최근에 typescript 3.7이 새롭게 공개돼서 해당 문서를 번역해서 간단히 소개해보도록 하겠습니다.

Optional Chaining


Kotlin을 접해봤다면 가장 반가운 문법이지 않을까 싶습니다. Kotlin의 가장 편리한 문법중 하나가 바로 이 Optional Chaining 이라고 생각합니다. Typescript 에도 3.7 부터 Optional Chaining 이 추가 된다고 하니 반가웠습니다.

Optional Chaining 을 간단하게 설명하면 기존의 복잡한 if문 을 줄여주는 문법이라 할 수 있을 것 같습니다. 기존에 null 또는 undefined 타입을 처리하기 위해서는 아래와 같이 복잡한 조건문을 사용해야 했습니다.

let x;
if(foo && foo.bar && foo.bar.baz) {
	x = foo.bar.baz()
}

하지만 Optional Chaning 을 사용하면 아래와 같이 간단하게 처리할 수 있습니다. null 또는 undefined 를 가질 수 있는 변수 뒤에 물음표를 붙이면 됩니다. 만약 물음표가 붙은 변수가 null 또는 undefined 값을 가진다면 코드의 실행을 중지하고 바로 undefined를 반환합니다.

let x = foo?.bar?.baz();
function print(doPrint: boolean): any {
    if (doPrint) {
        return;
    }
    console.log('print');
    return { print: print };
}
print(false)?.print(false)?.print(true).print(false);

// console : print
// console : print

Nullish Coalescing


Nullish Coalescing Operator(NCO) 는 새로운 ECMAScript 의 기능입니다. 기존의 (||) 연산자와 비슷한 기능을 하는데요, 물음표 두 개(??) 를 통해 왼쪽의 겂이 null 혹은 undefined 인지 확인합니다. 만약 왼쪽의 값이 null 혹은 undefined 라면 NCO 의 오른쪽에 위치한 값이 반환됩니다.  

const foo = undefined;
const bar = 'test';
const res = foo ?? bar;
// res === 'test'

기존의 || 연산자를 두고도 NCO가 새롭게 추가된 이유가 있습니다. 기존의 || 연산자는 null 또는 undefined 외에도 falsy(false 와 같은 값) 들을 처리하기 때문입니다.

const zero = 0;
const ten = 10;
const itShouldBeZero = zero || ten; // 10
const itShouldBeTen = zero ?? ten; // 0

javascript 에서 0이나 NaN은 false 와 동일 시 합니다. 따라서 원하는 || 연산자를 이용해 개발자가 의도한 동작을 하기 위해서는 비교적 복잡한 방법이 필요한 경우가 있었습니다. 하지만 NCO 는 정확히 null 또는 undefined 만 검사해주기 때문에 상황에 따라 적절히 두 연산자를 사용하면 좋을 것 같습니다.

Assertion Functions


타입스크립트는 Type Guard 를 통해 미지의 타입을 검사 하여 타입을 추론하고 안전한 로직을 작성 할 수 있습니다. 또한 Type Guard 를 통해 추론된 타입으로 IDE에서 제공되는 자동 완성과 인텔리센스 기능을 사용할 수 있습니다. 하지만 Type Guard 는 아래 예제와 같이 번거롭게 사용해야 하는 번거로움이 있었습니다.

function yell(str: any) {
    if (typeof str !== "string") {
        throw new TypeError("str should have been a string.")
    }
    return str.toUppercase();
    //         ~~~~~~~~~~~
    // error: Property 'toUppercase' does not exist on type 'string'.
    //        Did you mean 'toUpperCase'?
}

그래서 TypeScript 3.7 에서는 "assertion signatures" 가 추가되었습니다. assertion signatures 를 통해 assert 함수를 만들고, Type Guard 를 한 줄로 간단하게 사용할 수 있습니다.

function yell(str) {
    assert(typeof str === "string");

    return str.toUppercase();
    //         ~~~~~~~~~~~
    // error: Property 'toUppercase' does not exist on type 'string'.
    //        Did you mean 'toUpperCase'?
}

function assert(condition: any, msg?: string): asserts condition {
    if (!condition) {
        throw new AssertionError(msg)
    }
}

str 변수가 string 타입이 맞는지 검사하는 조건과 함께 assert 함수를 호출하면, 해당 assert는 type guard 로써 동작하기 때문에 그 뒤의 로직에서 str 은 string 타입으로 추론되어 수행됩니다.

또 다른 방식으로는 아래와 같이 타입을 검사하는 로직을 assert 함수 내부에 구현 할 수 있습니다.

function yell(str: any) {
    assertIsString(str);

    // Now TypeScript knows that 'str' is a 'string'.

    return str.toUppercase();
    //         ~~~~~~~~~~~
    // error: Property 'toUppercase' does not exist on type 'string'.
    //        Did you mean 'toUpperCase'?
}

function assertIsString(val: any): asserts val is string {
    if (typeof val !== "string") {
        throw new AssertionError("Not a string!");
    }
}

Recursive Type Aliases


기존 TypeScript에서 Type 별칭을 "재귀적으로" 참조하는 것은 불가능 했습니다. 타입 별칭은 컴파일 되며 스스로를 다른 타입으로 대체할 수 있어야 하기 때문입니다. 따라서 컴파일러는 아래와 같은 상황을 에러로 처리했습니다.

type Foo = Foo;

이는 합리적인 제약사항 이였습니다. 왜냐하면 Foo를 사용하는 곳에는 다시 Foo 로 대치 되고, 대체된 Foo 는 다시 Foo 로 대체 되어야 하고... 이렇게 무한하게 재귀적으로 타입이 대체되기 때문입니다.

 

따라서 TypeScript 3.6과 이전에는 아래와 같이 타입을 재귀적으로 선언 할 경우 컴파일 오류가 발생합니다.

type ValueOrArray<T> = T | Array<ValueOrArray<T>>;
//   ~~~~~~~~~~~~
// error: Type alias 'ValueOrArray' circularly references itself.

하지만 위와 같은 문제는 인터페이스로 우회하면 아무런 문제가 없도록 작성 할 수 있습니다.

type ValueOrArray<T> = T | ArrayOfValueOrArray<T>;

interface ArrayOfValueOrArray<T> extends Array<ValueOrArray<T>> {}

인터페이스(및 기타 객체 유형) 는 간접적인 레벨에서 타입을 정의하기 때문에 컴파일 결과로 나오는 게 없습니다. 결국 인터페이스를 거쳐 재귀적으로 선언된 타입 별칭은 컴파일되며 결국 사라지기 때문에 문제가 없습니다.

하지만 결국 재귀적으로 타입 별칭을 참조하는 것과 동일한 역할을 하지만, 위에서 말한 제약사항으로 인해 굳이 필요하지 않은 인터페이스를 하나 더 정의해서 우회해야 하는 불편함이 있었습니다.

 

그래서 TypeScript 3.7 에서는 이러한 제약사항을 없에고 재귀적으로 타입을 선언할 수 있게 되었습니다. 따라서 아래와 같이 정의해야 했던 타입 선언을

type Json =
    | string
    | number
    | boolean
    | null
    | JsonObject
    | JsonArray;

interface JsonObject {
    [property: string]: Json;
}

interface JsonArray extends Array<Json> {}

아래와 같이 다시 정의할 수 있습니다.

type Json =
    | string
    | number
    | boolean
    | null
    | { [property: string]: Json }
    | Json[];

이러한 기능을 통해 튜플 타입 별칭을 재귀적으로 참조 할 수도 있습니다. 기존에 오류로 판정 했던 아래 코드는 이제 유효한 코드가 됩니다.

type VirtualNode =
    | string
    | [string, { [key: string]: any }, ...VirtualNode[]];

const myNode: VirtualNode =
    ["div", { id: "parent" },
        ["div", { id: "first-child" }, "I'm the first child"],
        ["div", { id: "second-child" }, "I'm the second child"]
    ];

그 외의 주요 변경사항


  • --declaration flag and --allowJs flag
    • --declaration flag와 --allowJs flag를 함께 사용할 수 있습니다.
    • .js 에서 JsDOC을 정의하고 --declaration flag 와 함께 컴파일 하면 .js 파일으로부터 difinition(.d.ts) 파일을 만들 수 있습니다.
  • The useDefineForClassFields Flag and The declare Property Modifier
    • useDefineForClassFields flag 와 함께 ts 를 컴파일 하면 Object.defineProperty 로 Class Field 들이 정의됩니다.
    • 기존 TypeScript 가 동작하는 방식과 매우 달라지기 때문에 점진적으로 적용될 계획입니다.
    • TypeScript 3.7에서는 useDefineForClassFields flag 를 사용해야만 적용됩니다.
    • 기존 TypeScript 코드가 정상적으로 돌아가지 않을 수 있는 중요한 변경사항이라 자세하게 살펴볼 필요가 있을 것 같습니다.
    • 아직 TypeScript 컴파일러(3.7.2) 에 문제가 있어서 사용하지 않는 것 이 좋습니다. (관련 github 이슈 - 3.7.3 에서 수정 예정)
  • DOM Changes
  • Local and Imported Type Declarations Now Conflict
    • 기존에 interface를 동일한 이름으로 여러번 정의 할 수 있었지만 TypeScript 3.7 부터는 에러로 처리됩니다.

 

 

참고 자료


 

TypeScript 3.7 · TypeScript

Optional Chaining # Playground Optional chaining is issue #16 on our issue tracker. For context, there have been over 23,000 issues on the TypeScript issue tracker since then. At its core, optional chaining lets us write code where TypeScript can immediate

www.typescriptlang.org

 

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함