조건부 타입

정민교·2023년 10월 19일
0

typescript

목록 보기
17/17

📒조건부 타입

조건부 타입은 extends 와 삼항 연산자를 이용해 조건에 따라 각각 다른 타입을 정의하도록 돕는 문법입니다.

type A = number extends string ? number : string;

위 조건부 타입은 number extends string ? 같은 조건식이 있고 조건식이 참이라면 number를 반환, 아니라면 string을 반환합니다.

따라서 위 조건부 타입의 결과는 string 타입이 됩니다.

type ObjA = {
  a: number;
};

type ObjB = {
  a: number;
  b: number;
};

type B = ObjB extends ObjA ? number : string;

위 조건부 타입의 결과는 number 타입입니다.

✔️제네릭 조건부 타입

조건부 타입은 제네릭과 함께 사용할 때 위력이 극대화 됩니다.

type StringNumberSwitch<T> = T extends number ? string : number;

let varA: StringNumberSwitch<number>;
// string

let varB: StringNumberSwitch<string>;
// number

varA의 타입은number 타입이 되고 varB의 타입은 string 타입이 됩니다.

✔️분산 조건부 타입(분배법칙)

조건부 타입과 제네릭을 함께 사용할 때 제네릭 타입이 union 타입일 경우 분산 조건부 타입(조건부 타입 분배법칙)이 됩니다.

다음은 조건부 타입의 타입 변수에 number | string 타입을 할당한 경우입니다.

type StringNumberSwitch<T> = T extends number ? string : number;

(...)

let c: StringNumberSwitch<number | string>;
// string | number

타입 변수에 할당한 union 타입 내부의 모든 타입이 분리되어 분산됩니다.

  • StringNumberSwitch<number>
  • StringNumberSwitch<string>

그 후에 분산된 각 타입의 결과를 모아서 다시 union 타입으로 묶습니다.

never 타입은 union으로 묶일 경우 사라집니다. 공집합과 다른 집합의 합집합은 아무 의미가 없기 때문입니다.

  • 결과 : number | string

📌분산 조건부 타입(조건부 타입 분배법칙)에서 주의해야할 타입

👉boolean

분산 조건부 타입(조건부 타입 분배법칙)에서 boolean을 사용할 경우는 주의해야 합니다.

type Start = string | number | boolean;
type Result<Key> = Key extends string | boolean ? Key[] : never;
let n : Result<Start> = ['hi'];
n = [true];

n의 타입이 string[] | boolean[]이 될 것을 예상하였지만 string[] | true[] | false[]가 되었습니다.

booleantrue | false로 인식하기 때문입니다.

다음도 분산 조건부 타입(조건부 타입 분배법칙)을 이용한 타입입니다.

type IsString<T> = T extends string ? true : false;
type Result = IsString<'hi' | 3>;
// type Result = boolean

분산 조건부 타입이 수행되면 true | false가 결과가 됩니다. 따라서 결과적으로 Result의 type은 boolean이 되었습니다.

👉never

never도 분배 법칙의 대상이 됩니다. never도 union 타입이라고 생각하는 것이 좋습니다.

type R<T> = T extends string ? true : false;
type RR = R<never>;
// type RR = never;

never 타입을 분산 조건부 타입(조건부 타입 분배법칙)에 사용할 때는 결과가 never 타입이 된다고 외워두는 것이 좋습니다.

📌분산 조건부 타입이 동작하지 않도록 하기 위해서는 제네릭 타입을 배열로 감싸주면 됩니다.

👉boolean

위의 예제를 그대로 들고 왔습니다.

type IsString<T> = [T] extends [string] ? true : false;
type Result = IsString<'hi' | 3>;
// type Result = false

['hi' | 3][string] 타입의 하위 타입이 아니기 때문에 조건부 타입의 결과로 false가 됩니다.

👉never

type IsNever<T> = [T] extends [never] ? true: false;
type T = IsNever<never>;
// type T = true
type F = IsNever<'never'>;
// type F = false

제네릭 타입을 배열로 감싸주어 분산 조건부 타입(조건부 타입 분배법칙)이 발동되지 않도록 하였습니다.

[never][never] 타입의 하위 타입이기 때문에 type Ttrue가 됩니다.

✔️Exclude 조건부 타입 구현

분산 조건부 타입의 특징을 이용하여 union 타입으로부터 특정 타입만 제거하는 Exclude 타입을 정의할 수 있습니다.

type Exclude<T, U> = T extends U ? never : T;

type A = Exclude<number | string | boolean, string>;
  1. 타입변수에 할당한 union 타입 내부의 모든 타입이 분리됩니다.
  • Exclude<number, string>
  • Exclude<string, string>
  • Exclude<boolean, string>
  1. 각 분리된 타입이 조건부 타입에 의해 결과 타입으로 추론됩니다.
  • number
  • never
  • boolean
  1. 추론된 결과 타입들을 모두 union으로 묶습니다.
  • number | never | boolean

never는 공집합이므로 공집합과 어떤 집합의 합집합은 그대로이므로 never는 생략됩니다.

  • number | boolean

✔️infer 반환값 타입 추출

infer는 조건부 타입 내에서 특정 타입을 추론하는 문법입니다.

type ReturnType<T> = T extends () => infer R ? R : never;

type FuncA = () => string;

type FuncB = () => number;

type A = ReturnType<FuncA>;
// string

type B = ReturnType<FuncB>;
// number

ReturnType<FuncA>처럼 타입 변수 T에 FuncA를 할당합니다.

그리고 나서 infer는 타입변수 R이 조건부 타입을 만족할 수 있는 타입으로 알아서 추론됩니다.

() => string 함수 타입이 () => infer R 함수 타입의 서브타입이 되기 위해서 타입변수 R은 string 타입이 되면 됩니다.

이것을 해주는 것이 infer 키워드 입니다.

다음과 같이 추론이 불가능하다면 조건식을 거짓으로 판단합니다.

type ReturnType<T> = T extends () => infer R ? R : never;

type FuncA = () => string;

type FuncB = () => number;

type A = ReturnType<FuncA>;
// string

type B = ReturnType<FuncB>;
// number

type C = ReturnType<number>;
// 조건식을 만족하는 R추론 불가능
// never

number 타입이 함수 타입 () => infer R 의 서브 타입이 되는 것을 타입 변수 R이 어떤 타입이 되든 만족할 수 없습니다.

따라서 위 조건부 타입은 거짓으로 판별되어 never 타입을 반환합니다.

type PromiseUnpack<T> = T extends Promise<infer R> ? R : never;
// 1. T는 프로미스 타입이어야 한다.
// 2. 프로미스 타입의 결과값 타입을 반환해야 한다.

type PromiseA = PromiseUnpack<Promise<number>>;
// number

type PromiseB = PromiseUnpack<Promise<string>>;
// string

위는 Promise의 resolve 타입을 infer를 이용해 추출하는 예입니다.

profile
백엔드 개발자

0개의 댓글