성장, 그리고 노력

부족하더라도 어제보다 더 잘해지자. 노력은 절대 배신하지 않는다.

Typescript

[Typescript] Optional Chaining과 Nullish Coalescing

제이콥(JACOB) 2019. 12. 17. 19:47

 Typescript 3.7.0 버전부터는 많은 개발자들이 고대하던 몇 가지 스펙이 추가되었다. 그래서 간단하게나마 소개해 보려고 한다.

 

cf) vs-code 사용자라면 "Typescript 버전 3.7 + 워크 스페이스 버전 3.7" 설정을 잊지 말자!


Optional Chaining

  • typescript 3.7.0
  • vs-code 1.41

 ECMAScript 기능 중 많은 요구가 있었던 Optional Chaining이 TypeScript 3.7에서 추가되었다.

이미 많은 다른 언어에서는 지원되고 있었다. 물론 이름은 다를 수 있다.

  • C#: Null-conditional operator
  • Swift: Optional Chanining
  • CoffeeScript: Existential operator

What is optional chaining?

 null 또는 undefined가 있다면, 표현식 실행을 즉시(Immediately) 중지시킬 수 있다. 기존 조건식이 필요 없고 우리는 단순히 ?.라는 새로운 연산자만 추가하면 된다. 아래는 타입 스크립트를 사용한다면 많이 사용하는 표현식 중 하나이다.

if(data && data.item && data.item.id){
  // ...
}

 불필요한 nullish checks를 위해 && 오퍼레이터를 사용해 왔다.

 물론 저 코드를 쓰는 것은 어렵진 않지만, 반복되는 코드로 인해 가독성이 떨어진다. 결국 원하는 건 id가 있기를 바라는 거 하나인데.

optaional chaining을 적용하면 조금 더 간단하고 멋진 코드가 된다.

if(data?.item?.id){
  // ...
}

 

Syntax

 설명은 아래 예시와 함께 하겠다.

obj?.prop       // optional static property access
obj?.[expr]     // optional dynamic property access
func?.(...args) // optional function or method call

Base case

a?.b                          // undefined if `a` is null/undefined, `a.b` otherwise.
a == null ? undefined : a.b

a?.[x]                        // undefined if `a` is null/undefined, `a[x]` otherwise.
a == null ? undefined : a[x]

a?.b()                        // undefined if `a` is null/undefined
a == null ? undefined : a.b() // throws a TypeError if `a.b` is not a function
                              // otherwise, evaluates to `a.b()`

a?.()                        // undefined if `a` is null/undefined
a == null ? undefined : a()  // throws a TypeError if `a` is neither null/undefined, nor a function
                             // invokes the function `a` otherwise

기능

1. Optional Element에 접근할 수 있다.

function getArray<T>(arr?: T[]){
  return arr?.[0];
}

 위 예제를 기존의 방식으로 바꿔보면 아래와 동일한 코드이다.

function getArray<T>(arr?: T[]){
  return (arr === null || arr === undefined) ? undefined : arr[0];
}

 

2. Optional call이 가능하다.

async function makeRequest(url: string, log?: (msg: string) => void) {
    log?.(`Request started at ${new Date().toISOString()}`);
    // roughly equivalent to
    //   if (log != null) {
    //       log(`Request started at ${new Date().toISOString()}`);
    //   }

    const result = (await fetch(url)).json();

    log?.(`Request finished at at ${new Date().toISOString()}`);

    return result;
}

더 나아가기

Long short-circuiting

 단락이 트리거 될 때 .? 뒤에 오늘 모든 함수 호출, 프로퍼티 접근 등을 모두 건너뛴다.

a?.b.c(++x).d  // if `a` is null/undefined, evaluates to undefined. Variable `x` is not incremented.
               // otherwise, evaluates to `a.b.c(++x).d`.
a == null ? undefined : a.b.c(++x).d

Stacking

Optional Chaining뒤에 또 다른 Optional Chaining이 연속해서 올 수 있다.

a?.b[3].c?.(x).d
a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d
  // (as always, except that `a` and `a.b[3].c` are evaluated only once)

왜 null이 아닌 undefined로 평가할까?

 null은 프로퍼티가 없는 것으로 간주한다. null?.bundefined이다.

 

마이그레이션 후 단점이 있다면?

 undefined를 할당해서 옵셔널 체이닝을 사용할 수 없는 어처구니 없는 상황이 생기기 때문에 결국 둘 다 쓰게 된다. 하나로만 통일하고 싶었지만, 현실적으로는 어렵다.


Nullish Coalescing

  • Typescript - 3.7
  • @babel/plugin-proposal-nullish-coalescing-operator

 현재 ECMAScript stage 3에 있는 문법으로, 특별한 일이 없는 한 다음 명세에 포함될 것이다. 이 문법을 타입 스크립트 3.7 이상의 버전에서는 먼저 사용해 볼 수 있다.

 

현재(Present)

 우리는 아래와 같은 코드 작성에 익숙하고 결과에도 익숙하다.

const response = {
  settings: {
    nullValue: null,
    height: 400,
    animationDuration: 0,
    headerText: '',
    showSplashScreen: false
  }
};

const undefinedValue = response.settings.undefinedValue || 'some other default'; // result: 'some other default'
const nullValue = response.settings.nullValue || 'some other default'; // result: 'some other default'

 근데 이 결과는 종종 예상치 못한 결과를 이끌어 낸다. 아래 예제의 값을 예상해 보자.

const headerText = response.settings.headerText || 'Hello, world!';
const animationDuration = response.settings.animationDuration || 300;
const showSplashScreen = response.settings.showSplashScreen || true;
더보기

정답

Potentially unintended. '' is falsy, result: 'Hello, world!'

Potentially unintended. 0 is falsy, result: 300

Potentially unintended. false is falsy, result: true

 예상한 값이 맞았더라도, 과연 저 코드를 저런 값이 나오기 위해 작성했을까? 자바스크립트는 ||에 대한 대한으로 새로운 ?? 이라는 아이를 가지고 왔다.

 

What is Nullish Coalescing?

 굳이 해석해 보자면 “널한(?) 병합”이다. “Nullish”라는 단어가 없어서 정확하게는 잘 모르겠다.

 오히려 기존에 존재하던 널 병합 연산자가 맞는 표현 같다(https://en.wikipedia.org/wiki/Null_coalescing_operator).

 이 널 병합 연산자는 기존에 ||가 0, 비어있는 문자열 등까지 false로 평가했다면, 이번 ?? 이아이는 똑똑하게 nullundefiend만 평가한다.

Syntax

 일단 문법은 간단하다. 위에서 말한대로 ??|| 대신 써주면 된다.

위에 예제를 그대로 바꿔보면 이제 신세계가 펼쳐진다.

const response = {
  settings: {
    nullValue: null,
    height: 400,
    animationDuration: 0,
    headerText: '',
    showSplashScreen: false
  }
};

const undefinedValue = response.settings.undefinedValue ?? 'some other default'; // result: 'some other default'
const nullValue = response.settings.nullValue ?? 'some other default'; // result: 'some other default'
const headerText = response.settings.headerText ?? 'Hello, world!'; // result: ''
const animationDuration = response.settings.animationDuration ?? 300; // result: 0
const showSplashScreen = response.settings.showSplashScreen ?? true; // result: false

Transpiling

 

반응형