- Published on
Conditional Types
Conditional Types
Typescript의 Exclude, Extract, NonNullable, Parameters and ReturnType 유틸리티 타입은 내부적으로 어떻게 구현되어 있을까?
이 유틸리티 타입들은 Conditional Types 를 기반으로 구현되어 있다.
Conditional Types를 기반으로 어떻게 유틸리티 타입들이 구현되어 있는지 살펴본다.
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type NonNullable<T> = T extends null | undefined ? never : T;
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type T0 = Exclude<'a' | 'b' | 'c', 'a'>;
// type T0 = 'b' | 'c'
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'>;
// type T1 = 'a'
type T2 = NonNullable<string | number | undefined>;
// type T2 = string | number
type T3 = Parameters<(s: string) => void>;
// type T3 = [s: string]
Conditional Types 의 sytax는 다음과 같다.
이는 다음과 같은 의미를 가진다.
T가 타입 U에 assign이 가능하면, X를 리턴하고 그렇지 않으면 Y를 리턴한다.
Conditional Types을 통해 generic type의 조건에 따라 type을 결정할 수 있다.
Conditional Types 사용 예제
Conditional Types의 예제를 살펴보자.
조건에 따라 type이 결정된다.
type IsString<T> = T extends string ? true : false;
type I0 = IsString<number>; // false
type I1 = IsString<'abc'>; // true
type I2 = IsString<any>; // boolean
type I3 = IsString<never>; // never
type TypeName<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: T extends undefined
? 'undefined'
: T extends Function
? 'function'
: 'object';
type T0 = TypeName<string>; // 'string'
type T1 = TypeName<'a'>; // 'string'
type T2 = TypeName<true>; // 'boolean'
type T3 = TypeName<() => void>; // 'function'
type T4 = TypeName<string[]>; // 'object'
그러면 Conditional Types에 union type을 넘기면 어떻게 동작할까?
type T10 = TypeName<string | (() => void)>;
// "string" | "function"
type T11 = TypeName<string | string[] | undefined>;
// "string" | "object" | "undefined"
위의 결과에서 보는 것처럼 union type을 리턴한다. 이는 Conditional Types이 아래와 같이 Union 을 각각 분리하여 처리하기 때문이다.
T extends U ? X : Y
T => A | B | C
A | B | C extends U ? X : Y =>
(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
이제 Exclude의 동작하는 방식도 다음과 같이 이해할 수 있다.
type Exclude<T, U> = T extends U ? never : T;
type T4 = Exclude<"a" | "b" | "c", "a" | "b">
("a" extends "a" | "b" ? never : "a") // => never
| ("b" extends "a" | "b" ? never : "b") // => never
| ("c" extends "a" | "b" ? never : "c") // => "c"
never | never | "c" // => "c"
Conditional Types를 활용하여 아래 예제에서 처럼 function type과 non-function type을 쉽게 추출할 수 있다.
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
interface User {
id: number;
name: string;
age: number;
updateName(newName: string): void;
}
type T5 = FunctionPropertyNames<User>; // "updateName"
type T6 = FunctionProperties<User>; // { updateName: (newName: string) => void; }
type T7 = NonFunctionPropertyNames<User>; // "id" | "name" | "age"
type T8 = NonFunctionProperties<User>; // { id: number; name: string; age: number; }