<Demopeu/>

조건부 타입(Conditional Type)

조건부 타입(Conditional Type)

referencetypescriptconditional-typeinfer

삼항 연산자 조건문 형태를 가지는 타입 시스템이다.

string extends string ? 'yes' : 'no'

근데 이렇게만 보면 쓸데 없어 보이는데, 제네릭과 함께한다면 이야기가 달라진다. 아래 예시를 보자.

function removeSpaces(text:string){
    return text.replaceAll(' ', '');
}

let result = removeSpaces("hello world");

만약 여기에 null | undefined가 올 수 있다면 어떻게 할까?

function removeSpaces(text: string | null | undefined): string | undefined {
  if (typeof text === 'string') {
    return text.replaceAll(' ', '');
  }
  return undefined;
}

let result = removeSpaces("hello world");

이렇게 하면 문제가 해결된 것처럼 보이지만, result의 타입이 string | undefined가 돼버리는 것을 볼 수 있다. 그렇다면 제네릭을 사용해보자.

function removeSpaces<T>(text: T): T extends string ? string : undefined {
  if (typeof text === "string") {
    return text.replaceAll(" ", "") as any; // any를 사용하지 않을 경우, 함수 내부에서는 T의 타입을 알 수 없기 때문에 에러 발생
  } else {
    return undefined as any;
  }
}

하지만, 타입 단언을 사용하지 않으면 에러가 발생한다. 그런데 이렇게 할 거면 처음부터 any를 쓰면 되고 any를 쓰면 그냥 js를 쓰면 되는 것과 다를 게 없다.

이때, 타입 단언 보다는 함수 오버로딩을 사용하는 것이 더 좋다.

function removeSpaces<T>(text: T): T extends string ? string : undefined;
function removeSpaces(text: unknown) {
  if (typeof text === "string") {
    return text.replaceAll(" ", "");
  } else {
    return undefined;
  }
}

let result = removeSpaces("hi im winterlood");
// string

let result2 = removeSpaces(undefined);
// undefined

📌 분산적인 조건부 타입(Distributive Conditional Types)

분산적인 조건부 타입은 유니온 타입에 조건부 타입을 적용할 때, 각각의 타입에 대해 조건부 타입이 분산되어 적용되는 것을 말한다.

type StringNumberSwitch<T> = T extends number ? string : number;
type Test = StringNumberSwitch<string | number>; // 'string' | 'number'

이때 우리가 알고 있는 것은 유니온 타입이 더 크므로 false가 되어 number가 되어야 하는데, 신기하게도 결과는 'string' | 'number'가 된다. 이는 조건부 타입이 유니온 타입의 각 타입에 대해 분산되어 적용되기 때문이다.

만약, 분산적 조건부 타입을 막으려면 extends 양쪽에 대괄호를 감싸면 된다.

type NonDistributive<T> = [T] extends [number] ? string : number;
type Test2 = NonDistributive<string | number>; // number

📌 infer(추론)

조건부 타입은 infer 키워드를 사용하여 타입을 추론할 수 있다.

type Flatten<T> = T extends Array<infer I> ? I : T;

이 조건식을 참이 되도록 만들 수 있는 최적 타입을 추론하라는 의미이다. 만약 추론이 불가능한 식을 받으면 조건식을 거짓으로 판단한다. 아래 예시를 보자.

type PromiseUnpack<T> = T extends Promise<infer R> ? R : never;

type PromiseA = PromiseUnpack<Promise<number>>;
type PromiseB = PromiseUnpack<Promise<string>>;

이렇게 하면 Promise의 내부 타입을 추론할 수 있다.