Table of contents

6.6.0. Introduction

프로그램의 핵심은 인풋에 기반하여 결정을 내리는 것이다. 자바스크립트 프로그램도 다를 바 없다. 다만, 값을 쉽게 검사할 수 있다는 사실을 감안할 때, 프로그램적 결정은 인풋 타입에 기반하여 결정된다. 컨디셔널 타입(conditional types)은 타입의 인풋과 아웃풋 사이의 관계에 대해 표현할 때 도움을 준다.

-conditional-type-basic.ts-

interface Animal {
	live(): void;
}
interface Dog extends Animal {
	woof(): void;
}

type Example1 = Dog **extends Animal ? number : string;**

type Example2 = RegExp **extends Animal ? number : string;**

컨디셔널 타입은 (conditional ? trueExpression : falseExpression) 과 같은 형태로, 자바스크립트의 조건 표현식(conditional expression)과 닮아있다.

SomeType extends OtherType ? TrueType : FalseType;

extends 왼쪽에 있는 타입이 오른쪽에 있는 타입에 등록가능할 때, 우리는 첫번째 브랜치에 있는 타입(TrueType)을 반환받을 것이며, 반대의 경우에는 뒤의 브랜치에 있는 타입(FalseType)을 반환받을 것이다.

위 예제에서만 보면 컨디셔널 타입은 그다지 유용해보이지 않는다. 어차피 우리는 DogAnimal을 확장하고 있다는 사실을 아는 것이 어렵지 않다. 하지만 컨디셔널 타입을 제네릭과 함께 사용한다면 그 진가를 알 수 있다.

예를 들어, 아래와 같은 createLabel 함수를 만든다고 해보자.

interface IdLabel {
	id: number;
	// ...
}

interface NameLabel {
	name: string;
	// ...
}

function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
	throw 'unimplemented';
}

이 createLabel을 위한 오버로드는 인풋의 타입에 기반하여 선택하는 단일 자바스크립트 함수를 표현한다. 다음과 같은 사항에 주목해보자:

  1. 만약 라이브러리가 API 전체에 걸쳐 같은 종류의 선택을 계속 해야한다면, 이는 불필요한 부하를 줄 수 있다.
  2. 우리는 3개의 오버로드를 생성해야만 했는데, 만약 createLabel 함수가 새로운 타입을 받아야한다면 그에 따라 또다른 오버로드를 추가해줘야 할 것이다.

이러한 문제점을 안고 오버로드를 작성하는 대신, 우리는 컨디셔널 타입과 제네릭을 조합하여 다음과 같이 작성할 수 있다.

type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel;