일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- lexical scope
- function 문
- 자바스크립트
- type
- 정적스코프
- 함수 표현식
- function
- BIND
- variable object
- 객체
- scope chain
- This
- 함수
- react-router
- activation object
- 호이스팅
- react router
- moment.js
- 리액트 라우터
- JavaScript
- 화살표 함수
- hoisting
- lexical environment
- webstorm
- 실행컨텍스트
- function 표현식
- happy hacking
- vs code
- Execution Context
- Arrow function
- 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 |