본문 바로가기
프로그래밍 개발/Typescript

Typescript 조건부 타입

by Jinseok Kim 2021. 5. 10.
반응형

 

조건부 타입

 

 

기본 문법

type IsStringType<T> = T extends string ? 'yes' : 'no';
type T1 = IsStringType<string>
type T2 = IsStringType<number>
  • T에들어온 타입이 string이면 yes, number 이면 no가 되는 조건부 문법을 사용하는 기능이다. 
  • 자바스크립트의 삼항 연산자 문법을 사용하여 값을 다루는 것처럼 보이지만 타입스크립트에서의 위의 코드는 값이 아닌 타입에 대한 것이라고 봐야한다.

 

 

union과 조건부 타입

type IsStringType<T> = T extends string ? 'yes' : 'no';
type T1 = IsStringType<string | number>
type T2 = IsStringType<string> | IsStringType<number> ;

type Array2<T> = Array<T>;
type T3 = Array2<string | number>;
  • T1의 union의 <string | number>는 사실 T로 들어가면 T extends string에서 할당이 안되어 'no'가 될것 같았지만  조건부 타입에서는 string과 number에 따라 'yes' 'no' 둘 중 하나가 될 수 있게 되었다. 특별한 예외이다.
  • 즉 T1은 T2와 같은 형태를 가지고 있다고 보면된다. 둘의 결과는 똑같은 결과를 가진다.
  • 하지만 조건부 랜더링 없이 재네릭에서 union타입이 입력되면 T3는 type Array2<T> = Array<T> 로 인하여 string[] | number[]가 되는 것이 아니라 string 또는 number의 배열이 된다는 것이다.

 

 

 

 

Exclude와 Extract 타입

type T1 = number | string | never;
type Exclude<T, U> = T extends U ? never : T;
type T2 = Exclude<1 | 3 | 5 | 7, 1 | 5 | 9>;
type T3 = Exclude<string | number | (() => void), Function>;
type Extract<T, U> = T extends U ? T : never;
type T4 = Extract<1 | 3 | 5 | 7, 1 | 5 | 9>;
  • Exclude 타입을 보면  T가 U에 할당 가능할때 true을 반환하도록 되었다. 즉 T2에서  U인 1 | 5 | 9에 할당 가능한 T의 1과 5가 true로 반환되어 never로 인하여 삭제되고 false을 반환한 3과 7은 T에 삽입되어 T2의 타입은 3과 7이 된다.
  • 마찬가지로 T3 또한 함수Function로 할당되지 못하는 타입 string과 number 만이 T로 삽입되고 함수인 (() => void)는 never로 인하여 제거된다.
  • 반대로 Extract 타입은 할당 가능한 1, 5이 T4의 타입이 된다.

 

 

 

Returntype 

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

function f1(s: string): number {
    return s.length;
}
type T2 = ReturnType<typeof f1>;
  • Returntype은 T가 함수일때 T함수 반환 타입을 뽑아주는 역할을 한다. 타입스크립트에 내장된 기능이며 type문 생략 가능하다.
  • (...arg: any[]) => infer R ? R: any가 그 역할을 해준다. T가 함수에 할당 가능한 것이면 R을 사용하겠다라는 의미이다.
  • 여기서 R은 함수의 반환타입을 의미한다. infer라는 키워드 덕분에 함수의 반환 타입을 알 수 있고 R이라는 변수에 반환 타입을 담을 수 있다는 것도 중요하다. T1에는 R에 string이라는 반환 타입이 담기게 되었다.
  • T2는 재네릭에서 값이 아닌 타입만 가져올 수 있기 때문에 typeof라는 키워드를 사용하여 실제 함수인 f1을 가져와 타입을 추출해내어 Return 타입의 재네릭에 담가주었다.
  • f1은 number을 반환하기 때문에 T2는 number의 타입을 가지게 된다.

 

 

infer 활용

type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U>
? U
: T;
type T0 = Unpacked<string>; //자기 자신 string을 타입으로 반환
type T1 = Unpacked<string[]>; //배열이기에 첫번째 조건부에서 U에 string 타입 반환
type T2 = Unpacked<() => string>; // 함수이기에 두번째 조건부에서 U에 반환 string 타입 반환
type T3 = Unpacked<Promise<string>>;// Promise 이기에 세번째 조건에서 U에 string 타입 반환
type T4 = Unpacked<Promise<string>[]>;// []이 있기에 첫번째 조건부에서 걸려 Promise가 타입으로 반환
type T5 = Unpacked<Unpacked<Promise<string>[]>>;
//두번 Unpacked을 호출하였기에 처음에는 []로 인하여 첫번째 조건부에 걸려 Promise가 반환되지만
//한번 더 Unpacked을 호출한 영향으로 세번째 조건부에 걸려 Promise의 string이 U에 타입으로 반환된다.

 

 

 

 

 

타입 조건부 활용 - 유틸리티 함수

type StringPropertName<T> = {
    [K in keyof T]: T[K] extends string ? K : never;
}[keyof T];

interface Person {
    name: string;
    age: number;
    nation: string;
}

type T1 = StringPropertName<Person>; //T1의 타입은 name과 nation이다.

type StringProperties<T> = Pick<T, StringPropertName<T>>;
type T2 = StringProperties<Person>;
  • StringPropertName 타입은 값이 문자열인 속성 이름을 추출하는 유틸리티 함수이다.
  • 맵드 타입을 이용하여 받은 T 인터페이스는 in key of을 통해 자신의 키 이름을 T[K]의 K에 넣어 조건부 타입을 이용하여 string인 문자열 속성 이름이 true로 반환하게 하도록 하였다. 나머지 false 반환 값들은 never로 인해 제거된다. 
  • 또 StringProperties 타입은 Pick 키워드를 사용하여 T에 들어온 인터페이스를 이용하여 T의 인터페이스 중 StringPropertName<T>의 타입만을 T 인터페이스에서 선택하여 T2의 타입 값들로 지정해준다.
  • T2는 const T2 ={ name: string; nation: string; }이 되었다.

 

 

 

Omit 타입

type Omit<T, U extends keyof T> = Pick<T, Exclude<keyof T, U>>

interface Person {
    name: string;
    age: number;
    nation: string;
}

type T1 = Omit<Person, 'nation' | 'age'>;

 

  • Omit 또한 타입스크립트의 내장된 기능이며 type문을 생략 가능하다.
  • Pick과 Exclude 키워드를 조합하여 기능을 실행한다. U extends T에서 U에는 T에 할당 가능한 속성만이 들어갈 수 있으며 Exclude에서 전체 T의 속성에서 U의 타입 속성을 지우고, Pick을 이용하여 U 타입들을 제거한 T의 타입 속성 값만을 T에서 골라내는 원리이다.
  • T1은 const T1 = { name: string; }라고 타입이 지정되었다.

 

 

 

Overwrite

type Overwrite<T, U> = { [P in Exclude<keyof T, keyof U]: T[P] } & U;

interface Person {
    name: string;
    age: number;
}

type T1 = Overwrite<Person, { nation: string; age: string; };
const p: T1 = {
    name: 'jinseok',
    age: 26, //타입 오류. age의 타입은 T1에서 string이다.
    nation: 'korea'
}
  • 타입스크립트에 내장된 객체는 아니어서 type문 생략은 가능하지는 않다.
  • 중복되는 타입을 하나로 합쳐주고 중복되지 않는 타입 또한 더해주는 기능을 담담하고 있다.
  • Overwrite로 T와 U을 받아 일단 Exclude을 통해 U의 타입들을 T 타입에서 제거해주고 곧바로 맵드 타입에서 T[P]문법을 이용하여 Exclude에서 걸러진 T의 타입 값들을 넣어주는 과정을 통해 중복된 타입 값들을 처리한다.
  • 그리고 마지막에 U와 &을 이용하여 교집합 시켜주어 중복되지 않은 타입을 더해준다.

 

 

 

반응형

'프로그래밍 개발 > Typescript' 카테고리의 다른 글

Typescript 타입 가드  (0) 2021.05.11
Typescript 타입 추론  (0) 2021.05.11
Typescript 맵드 타입(mapped type)  (0) 2021.05.10
Typescript 재네릭  (0) 2021.05.07
Typescript 타입 호환성  (0) 2021.05.07

댓글