Conditional Types | TypeScript

Bori·2023년 12월 10일
0

어쨌든 공부

목록 보기
36/40

조건부 타입(conditional type)은 자바스크립트의 삼항연산자(condition ? trueExpression : falseExpression)와 비슷한 형태를 가집니다.

SomeType extends OtherType ? TrueType : FalseType

extends 키워드의 왼쪽에 있는 타입(SomeType)을 오른쪽에 있는 타입(OtherType)에 할당할 수 있다면 TrueType 이 되고 그렇지 않다면 FalseType이 됩니다.

// 제네릭 T가 string이라면 string array, 아니면 number array 타입
type IsStringType<T> = T extends string ? string[] : number[];

type StringType = IsStringType<string>; // type StringType = string[]
type NumberType = IsStringType<number>; // type NumberType = number[]

Conditional Type Constraints(조건부 타입으로 제한하기)

조건부 타입으로 제네릭의 타입을 제한할 수 있습니다.

// 제네릭 T를 boolean 타입으로 제한
// T가 true 라면 data는 string 타입, false라면 number 타입 지정
interface IsStringData<T extends boolean> {
    data: T extends true ? string : null;
    isString: T;
}

const stringData: IsStringData<true> = {
    data: '문자열', // data 타입은 string
    isString: true,
}

const nullData: IsStringData<false> = {
    data: null, // data 타입은 null
    isString: false,
}

Inferring Within Conditional Types(조건부 타입 내에서 추론하기)

조건부 타입의 조건식이 참으로 평가될 때 infer 키워드를 사용하여 타입을 추론할 수 있습니다.
따라서, infer 키워드는 조건부 타입의 extends 절에서만 사용할 수 있습니다.

infer 타입의 기본 구조는 다음과 같습니다.

// U가 추론할 수 있는 타입이면 X 타입, 아니면 Y 타입
T extends infer U ? X : Y

infer 키워드의 원리는 런타임에서 타입을 추론할 수 있도록 하고, 추론한 타입을 infer 타입 파라미터 U에 할당합니다. 그리고 조건부 타입에 의해 조건식이 참이라면 파라미터(U)를 아니라면 무시하는 것이 기본 동작입니다.

// 제네릭 T의 타입을 받아와서 R이라는 변수에 할당
type TestType<T> = T extends infer R ? R : null;

// test 타입은 string으로 추론
let test: TestType<string>;


type ArrayType<T> = T extends (infer R)[] ? R : unknown;

// stringType 타입은 string으로 추론
type stringType = ArrayType<string[]>;	
// unknownType 타입은 unknown으로 추론
type unknownType = ArrayType<string>;

조금 더 복잡한 예시로는 ReturnType이 있습니다.
ReturnTypeinfer 키워드를 사용하여 유틸리티 타입으로 만들어졌습니다.

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

infer 키워드가 어떻게 ReturnType을 유틸리티 타입으로 만들었을까요?

먼저, 타입을 직접 정의하여 ReturnType을 작성해봅시다.

// ReturnType의 타입을 string 타입으로 명시
type ReturnType<T extends (...args: any) => any> = string;

const testFunction = (param: number) => {
    return param.toString();
}

// test 타입은 string
const test: ReturnType<typeof testFunction> = 'String';

ReturnType<T>의 타입이 string이므로 변수 test의 값으로 string 타입인 'String' 문자열을 할당할 수 있습니다.

함수 testFunction의 반환값이 toString에 의해 string 타입이 되었으므로 문제가 없지만 만약 testFunction의 반환값이 string이 아닌 다른 타입이라면 위 예시에 ReturnType<T>의 타입을 string 타입으로 직접 명시한 것처럼 각 타입에 맞게 수정을 해야합니다.

또는 다음과 같이 유니온 타입으로 명시해줘야 합니다.

// 유니온 타입을 이용하여 ReturnType의 타입을 string 또는 number 타입으로 명시
type ReturnType<T extends (...args: any) => any> = string | number;

효율적으로 타입을 명시하기 위해 infer 키워드를 이용하여 타입을 직접 정의하는 것이 아니라 타입을 추론 시킵니다.

따라서, 각 타입에 따라 정의하거나 수정할 필요없이 추론에 의해 함수의 반환 타입에 의한 타입을 만들 수 있습니다.

참고

0개의 댓글