| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 |
- This
- 함수
- react router
- react-router
- hoisting
- happy hacking
- type
- lexical scope
- Arrow function
- 자바스크립트
- 함수 표현식
- 정적스코프
- JavaScript
- scope chain
- moment.js
- BIND
- function 문
- function 표현식
- 화살표 함수
- variable object
- 리액트 라우터
- function
- activation object
- lexical environment
- webstorm
- 호이스팅
- Execution Context
- vs code
- 실행컨텍스트
- 객체
- Today
- Total
Pandaman Blog
[Typescript] Conditional Types 본문
Conditional Types
Conditional Types은 조건에 따라서 타입 변수에 할당할 수 있는 타입을 결정할 수 있다.
T extends U ? X : Y
Conditional Type은 3항 연산자 구문을 사용하고 있다. T extends U 조건이 충족되면 타입 X가 되고, 그렇지 않으면 타입 Y가 된다.
한번 예제를 살펴보자.
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
type Example1 = Dog extends Animal ? number : string;
Dog 인터페이스는 Animal를 확장해서 생성한 타입이다. Example1를 확인해보면 Dog extends Animal가 true 면 number타입이고, false면 string가 된다. Dog는 Animal를 extends 했기때문에 Example1 타입은 number가 된다.
여기서 주목할 점은 제약 조건(extends Animal ?)을 추가함으로써 타입을 결정할 수 있다는 점이 되겠다. 사실 위의 예제보다는 제네릭을 통해서 타입을 결정할때 유용하게 사용될 수 있다.
generic에서 conditional types
제네릭을 통해서 우리는 conditional type을 유용하게 사용할 수 있다.
링크
type StringOrNumber<T extends string | number> = T extends string ? 'this is string' : 'this is number';
type MustBeString = StringOrNumber<'hi'>;
type MustBeNumber = StringOrNumber<123>;
StringOrNumber 타입의 타입인자 T에는 string 또는 number 타입을 할당할 수 있다. 그리고 조건부를 확인해보면 T 가 string 가 참인 경우 'this is string'인 리터럴 타입이되고 아니라면 'this is number'인 리터럴 타입이 된다.
지난 시간에 타입을 순회하면 새로운 타입을 반환하는 Mapped type을 배웠다. 아래는 Mapped type을 사용한 코드이다.
interface Person {
경력: number;
취미: string;
}
type FrontChapter = "bk" | "lio" | "blue" | "dali" | "coo";
const frontChapter: RecordTest<FrontChapter, Person> = {
bk: { 경력: 10, 취미: "게임" },
lio: { 경력: 9, 취미: "독서" },
blue: {경력: 8, 취미: "축구"},
dali: { 경력: 7, 취미: "헬스" },
coo: { 경력: 6, 취미: "청소" },
};
type RecordTest<K extends string , T> = {
[P in K]: T;
};
여기서 FrontChapter 에 poodle 리터럴 타입을 추가하고 싶다. 속성이 poodle 인 경우는 털빠짐, 종류 속성을 갖는 타입 Animal이 되도록 하고 나머지는 기존과 동일하게 Person 타입을 갖게 하고 싶다.
링크
interface Person {
경력: number;
취미: string;
}
interface Animal {
털빠짐: '많음' | '보통' | '적음';
종류: string;
}
type FrontChapter = "bk" | "lio" | "blue" | "dali" | "coo" | 'poodle';
const frontChapter: RecordTest<FrontChapter> = {
bk: { 경력: 10, 취미: "게임" },
lio: { 경력: 9, 취미: "독서" },
blue: {경력: 8, 취미: "축구"},
dali: { 경력: 7, 취미: "헬스" },
coo: { 경력: 6, 취미: "청소" },
poodle: { 털빠짐: '적음', 종류: '푸들' }
};
type RecordTest<K extends string> = {
[P in K]: P extends 'poodle' ? Animal: Person;
};
이전과 다른점은 P extends 'dog'를 추가했다는 점이다. 속성 키 타입인 P가 poodle 라는 조건에서 참인 경우 Animal가 되고 아닌 경우 Person가 된다. Mapped type과 Conditional type은 이렇게 조합하여 자유롭게 사용가능하다.
Distributive Conditional Types(분배 조건부 유형)
조건부 형식이 제네릭 타입에서 작동할 때 유니온 타입이 지정되면 Distributive type 이 된다고 한다. 음 이게 무슨말일까.
순서대로 확인해보자.
- T extends U ? X : Y 가 있다고 가정
- 매개변수 타입 T 는 A | B | C 유니온 타입이 전달된다고 가정
- 다음과 같이 해석된다. (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
위와 같은 순서대로 Distributive type 동작이 동작한다.
예제를 살펴보며 다시 Distributive type 동작을 살펴보자.
type NonNullable<T> = T extends null | undefined ? never : T;
NonNullable 타입은 타입인자 T를 갖으며 T가 null | undefined 라면 never가 되고 아니라면 T를 그대로 반환한다.
여기서 never 타입은 T extends null | undefined 조건이 참인 경우 대한 타입을 제거하는데 사용된다.
type PhoneNumberTypes = number | number[] | null | undefined;
type NonNullableEmailAddress = NonNullable<PhoneNumberTypes>;
다음과 같이 맵핑된다.
type NonNullablePhoneNumberTypes =
| NonNullable<number>
| NonNullable<number[]>
| NonNullable<null>
| NonNullable<undefined>;
다시 NonNullable 타입을 풀어서 작성하면
type NonNullablePhoneNumberTypes =
| (number extends null | undefined ? never : number)
| (number[] extends null | undefined ? never : number[])
| (null extends null | undefined ? never : null)
| (undefined extends null | undefined ? never : undefined);
가 된다.
type NonNullablePhoneNumberTypes = number | number[] | never | never;
never는 가장 하위 타입이므로 never 에는 어떤 타입도 할당할 수 없다. 즉 타입 NonNullablePhoneNumberTypes는 number | number[] 타입만 할당가능한 타입이다. 따라서 never 타입은 유니온타입에서 생략가능하게 된다.
type NonNullablePhoneNumberTypes = number | number[];
따라서 결론적으로 위의 타입을 얻을 수 있다.
infer keyword
위에서는 조건부 결과에 의한 타입추론에 대한 설명이었다면, 이번에는 조건부 내에서의 타입을 추론하기 위해 infer 키워드를 사용해야한다.
그럼 조건부내에서 타입 추론을 할 필요가 있을까. 라는 생각이 들었다.
결론은 필요하다.
type GetReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer Return
? Return
: never;
GetReturnType 타입은 인자로 전달한 함수의 반환 타입을 반환하는 타입이다.
T extends (...args: any) => infer Return ? Return : never
infer 키워드 뒤의 타입 Return는 타입매개변수 T의 반환타입을 추론한 타입이다. T extends (...args: any) => infer Return 조건에 참이 된다면 타입 Return 반환한다.
type NumberReturnType = GetReturnType<() => number>;
// ?
type StringReturnType = GetReturnType<(x: string[]) => string>;
// ?
type BoolsReturnType = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
// ?
예상대로 모두 타입매개변수의 반환타입을 반환한다.
'Front end > Typescript' 카테고리의 다른 글
| [Typescript] Utility Types - 2 (0) | 2022.03.27 |
|---|---|
| [Typescript] Mapped Types (0) | 2022.02.24 |
| [Typescript] Utility Types - 1 (0) | 2022.02.21 |
| [Typescript] 함수 (0) | 2021.12.19 |
| [Typescript] Type Guard (0) | 2021.12.18 |