TypeScript, 열거 타입은 열거형보다 객체 방식이 보다 권장됨

열거 타입을 정의하기 위해 TypeScript는 열거형을 제공합니다. 예를들어 다음 코드가 가능합니다.

const enum EDirection {
  Up,
  Down,
  Left,
  Right,
}

function walk(dir: EDirection) {}
walk(EDirection.Left);

위의 코드와 비슷한 맥락으로, 이번에는 열거형을 사용하지 않고 객체 방식으로 작성하면 다음과 같습니다.

const ODirection = {
  Up: 0,
  Down: 1,
  Left: 2,
  Right: 3,
} as const;

type Direction = typeof ODirection[keyof typeof ODirection];
function run(dir: Direction) {}
run(ODirection.Right);

TypeScript, {}

{}는 빈 객체입니다. 하지만 TypeScript에서는 nullundefined를 제외한 모든 값을 의미하는 특수한 타입이기도 합니다.

그래서 다음 코드가 있다고 한다면 …

type A = {}
let a: A;

다음 코드는 모두 문제가 없습니다.

a = "hello";
a = 123;
a = true;
a = [];
a = {};
a = () => {};

그러나 다음 코드는 에러입니다.

a = null;
a = undefined;

그럼 {} 타입은 어디에 쓰는 물건일까요? 바로 NonNullable 입니다. NonNullable은 다음처럼 작성되어져 있습니다.

type NonNullable<T> = T & {}

NonNullable은 타입에서 nullundefined를 제외시킨 타입을 만들어줍니다. 아래코드처럼요.

type T0 = NonNullable<string | number | undefined>;
// type T0 = string | number

type T1 = NonNullable<string[] | null | undefined>;
// type T1 = string[]

NonNullable의 구현은 TypeScript의 버전에 따라 다른데요. 좀더 오래된 4.8에서는 다음처럼 구현되어 있다는 점도 알아두시면 좋습니다.

type NonNullable<T> =
    T extends null | undefined
        ? never
        : T;

TypeScript, keyof any ?

타입스크립트로 작성된 코드를 보면 keyof any가 무엇인가… 라는 의문이 생깁니다. 결론부터 이야기하면 string | number | symbol 타입입니다. 즉, 객체를 구성하는 키가 가질 수 있는 타입입니다. 다음 코드를 보겠습니다.

type Person = {
    name: string;
    age: number;
};

type K = keyof Person;

K"name" | "age" 타입입니다. 즉, Person 객체의 key 값으로 구성된 유니언 타입입니다. 이러한 keyof의 작동을 봤을때 keyof any는 객체의 key 값으로 구성되는 타입입니다. 즉, 앞서 봤던 string | number | symbol이 됩니다. Person의 경우 키 값의 타입은 string 뿐입니다. 배열은 키 값의 타입으로 number를 갖습니다. 특수한 경우 키값의 타입으로 symbol을 가질 수도 있구요. 그래서 객체를 구성하는 키가 가질 수 있는 타입은 string | number | symbol 인 것입니다.

keyof any가 객체의 키 타입이라는 것이라는 것을 즉시 유추하기는 일반적으로 어렵습니다. 그래서 TypeScript는 keyof any와 동일한 타입으로 다음과 같은 PropertyKey 타입을 미리 정의두고 있습니다.

type PropertyKey = string | number | symbol;

TypeScript, 클래스와 생성자 시그니쳐(Constructor Signature)

다음과 같은 클래스가 있습니다.

class Person {
    constructor(name: string) {}
}

위 클래스의 생성자를 눈 여겨 보기 바랍니다. 클래스의 생성자는 new 연산자와 함께 클래스를 함수처럼 호출하면 실행되는 함수입니다. 이렇게 생성되는 생성자의 일반적인 시그니쳐는 다음과 같습니다.

new (...args) => ReturnType

앞서 봤던 Person 클래스에 대한 생성자 시그니쳐는 다음과 같습니다.

new (name: string) => Person

사실 Person 클래스의 생성자 시그니처는 간단히 다음처럼 얻을 수 있습니다.

type T = typeof Person;

즉, T는 다음과 같게 됩니다.

type T = new(name: string) => Person;

JavaScript, ??= 와 ||= 그리고 &&= 연산자에 대하여

몰라도 내 코드 작성에 전혀 문제가 되지 않는 연산자이지만 다른 사람의 코드를 분석하다 보면 볼 수 있는 연산자이므로 알고는 있어야 하기에 정리해 봅니다.

먼저 ??= 연산자는 Nullish Coalescing Assignment(널 병합 대입 연산자) 입니다. 좌변이 null 이거나 undefined 일때만 우변을 대입합니다. 코드로 보면 …

x ??= y;

위의 코드는 다음 코드와 동일한 코드이며 아래 코드로 설명을 대신합니다.

if (x === null || x === undefined) {
  x = y;
}

위의 코드는 다음 코드와 같습니다.

x = x ?? y;

참고로 ?? 연산자는 Nullish Coalescing Operator(널 병합 연산자)자로 ??= 연산자의 형제이기도 합니다. 위의 코드는 다음과 같습니다.

const result = (a === null || a === undefined) ? b : a;

자바스크립트 특유의 falsy 값으로 판정되는 false, 빈문자열, 0, NaN, null, undefined 값 중 오직 null, undefined 만을 특별하게 취급해주는 것이 바로 ?? 연산자와 ??= 연산자입니다.

??= 연산자를 살펴보다 보면 ||= 연산자와 &&= 연산자도 함께 살펴봐야 혼란이 없습니다. 먼저 ||= 연산자는 좌변이 falsy 값이면 대입합니다. 예를 들어 다음 코드를 보면 …

let a = 0;
a ??= 100;

console.log(a); // 0

&&= 연산자는 ||= 연산자와는 반대로 좌변이 truthy(falsy 값이 아닌 값)이면 대입합니다. 예들 들어 다음 코드를 보면 …

let z = "abc";
z &&= "xyz";
console.log(z); // "xyz"