Skip to content

Commit 89eac97

Browse files
Merge pull request #143 from bumkeyy/translate-conditional-types-ko
Co-authored-by: dvlprsh <[email protected]>
2 parents 568175b + bd9e1a3 commit 89eac97

File tree

1 file changed

+270
-0
lines changed

1 file changed

+270
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
---
2+
title: Conditional Types
3+
layout: docs
4+
permalink: /ko/docs/handbook/2/conditional-types.html
5+
oneline: "타입 시스템에서 if문 처럼 동작하는 타입 생성하기."
6+
---
7+
8+
대부분 유용한 프로그램의 핵심은, 입력에 따라 출력이 결정되어야 한다는 것입니다.
9+
JavaScript 프로그램도 크게 다르진 않지만, 값의 타입을 쉽게 검사할 수 있다는 사실을 고려할 때, 출력에 대한 결정은 또한 입력의 타입에도 기반합니다.
10+
_조건부 타입_ 은 입력과 출력 타입간의 관계를 설명하는 데 도움을 줄 수 있습니다.
11+
12+
```ts twoslash
13+
interface Animal {
14+
live(): void;
15+
}
16+
interface Dog extends Animal {
17+
woof(): void;
18+
}
19+
20+
type Example1 = Dog extends Animal ? number : string;
21+
// ^?
22+
23+
type Example2 = RegExp extends Animal ? number : string;
24+
// ^?
25+
```
26+
27+
조건부 타입은 JavaScript에 있는 삼항 연산자 조건문 (`condition ? trueExpression : falseExpression`) 같은 형태를 가집니다.
28+
29+
```ts twoslash
30+
type SomeType = any;
31+
type OtherType = any;
32+
type TrueType = any;
33+
type FalseType = any;
34+
type Stuff =
35+
// ---cut---
36+
SomeType extends OtherType ? TrueType : FalseType;
37+
```
38+
39+
`extends`를 기준으로 왼쪽에 있는 타입이 오른쪽 타입에 할당할 수 있다면 첫 번째 분기("참"값 분기)를, 그렇지 않다면 뒤의 분기("거짓"값 분기)를 얻게 됩니다.
40+
41+
`Dog extends Animal` 에 따라 `number``string`인지 알려주는 것 말곤, 위의 예제에서 조건부 타입은 그다지 유용해 보이지 않습니다!
42+
하지만 제네릭과 함께 사용될 때 조건부 타입은 강력한 힘을 갖습니다.
43+
44+
예를 들어, 다음 `createLabel` 함수를 살펴보겠습니다.
45+
46+
```ts twoslash
47+
interface IdLabel {
48+
id: number /* some fields */;
49+
}
50+
interface NameLabel {
51+
name: string /* other fields */;
52+
}
53+
54+
function createLabel(id: number): IdLabel;
55+
function createLabel(name: string): NameLabel;
56+
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
57+
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
58+
throw "unimplemented";
59+
}
60+
```
61+
62+
createLabel의 오버로드들은 입력 타입에 따른 단일 JavaScript 함수를 나타냅니다. 다음을 주목하세요.
63+
64+
1. 만약 라이브러리가 매번 API 전체에서 비슷한 종류의 함수를 만들어야 한다면 번거로워집니다.
65+
2. 우린 3가지 오버로드 즉, 각 케이스별로 _확실한_ 타입을 가지거나 (각각 `number``string`) 그리고 일반적인 케이스(`string | number`) 가져야 합니다. `createLabel`의 새로운 타입을 다루기 위해선 오버로드의 수는 기하급수적으로 증가합니다.
66+
67+
대신에 조건부 타입으로 로직을 인코딩할 수 있습니다.
68+
69+
```ts twoslash
70+
interface IdLabel {
71+
id: number /* some fields */;
72+
}
73+
interface NameLabel {
74+
name: string /* other fields */;
75+
}
76+
// ---cut---
77+
type NameOrId<T extends number | string> = T extends number
78+
? IdLabel
79+
: NameLabel;
80+
```
81+
82+
조건부 타입을 사용하면 단일 함수까지 오버로드 없이 단순화 시킬 수 있습니다.
83+
84+
```ts twoslash
85+
interface IdLabel {
86+
id: number /* some fields */;
87+
}
88+
interface NameLabel {
89+
name: string /* other fields */;
90+
}
91+
type NameOrId<T extends number | string> = T extends number
92+
? IdLabel
93+
: NameLabel;
94+
// ---cut---
95+
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
96+
throw "unimplemented";
97+
}
98+
99+
let a = createLabel("typescript");
100+
// ^?
101+
102+
let b = createLabel(2.8);
103+
// ^?
104+
105+
let c = createLabel(Math.random() ? "hello" : 42);
106+
// ^?
107+
```
108+
109+
### 조건부 타입으로 제한하기
110+
111+
종종, 조건부 타입의 검사에서 새로운 정보를 얻을 수 있습니다.
112+
타입 가드가 더 구체적인 타입으로 좁혀주듯이, 조건부 타입의 "참"값 분기는 대조하는 타입에 따라서 제네릭을 더 제한할 수 있습니다.
113+
114+
다음 예를 살펴보겠습니다.
115+
116+
```ts twoslash
117+
// @errors: 2536
118+
type MessageOf<T> = T["message"];
119+
```
120+
121+
위 예제에서, `T``message` 프로퍼티를 가지고 있는지 알 수 없기 때문에 TypeScript에서 오류가 발생합니다.
122+
`T`의 타입을 제한해서 TypeScript가 더이상 오류를 내지 않도록 만들 수 있습니다.
123+
124+
```ts twoslash
125+
type MessageOf<T extends { message: unknown }> = T["message"];
126+
127+
interface Email {
128+
message: string;
129+
}
130+
131+
type EmailMessageContents = MessageOf<Email>;
132+
// ^?
133+
```
134+
135+
하지만 `MessageOf` 가 아무 타입이나 받을 수 있고, `message` 프로퍼티가 없으면 `never` 타입으로 결정하도록 만들 수 있을까요?
136+
여기서 제약 조건을 외부로 옮기고, 조건부 타입을 적용하면 가능합니다.
137+
138+
```ts twoslash
139+
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
140+
141+
interface Email {
142+
message: string;
143+
}
144+
145+
interface Dog {
146+
bark(): void;
147+
}
148+
149+
type EmailMessageContents = MessageOf<Email>;
150+
// ^?
151+
152+
type DogMessageContents = MessageOf<Dog>;
153+
// ^?
154+
```
155+
156+
"참"값 분기내에서는 TypeScript는 `T``message` 프로퍼티를 가지고 _있을 것을_ 알 수 있습니다.
157+
158+
또 다른 예제에서 배열 타입이면 배열의 개별 요소 타입으로 평탄화 시키지만, 배열 타입이 아니면 그대로 유지하는 `Flatten` 타입을 만들 수 있습니다.
159+
160+
```ts twoslash
161+
type Flatten<T> = T extends any[] ? T[number] : T;
162+
163+
// Extracts out the element type.
164+
type Str = Flatten<string[]>;
165+
// ^?
166+
167+
// Leaves the type alone.
168+
type Num = Flatten<number>;
169+
// ^?
170+
```
171+
172+
`Flatten`에 배열 타입이 주어지면, `number`를 사용한 인덱스 접근을 통해 `string[]`의 요소 타입을 가져올 수 있습니다.
173+
그렇지 않으면, 주어진 타입을 반환합니다.
174+
175+
### 조건부 타입 내에서 추론하기
176+
177+
위에서 제약 조건을 가진 조건부 타입을 이용해서 타입을 추출할 수 있다는 점을 살펴봤습니다.
178+
이 부분은 조건부 타입을 더 쉽게 만드는 평범한 작업이 됩니다.
179+
180+
조건부 타입은 `infer` 키워드를 사용해서 "참"값 분기에서 비교하는 타입을 추론할 수 있습니다.
181+
예를 들어, `Flatten`에서 인덱싱된 접근 타입으로 "직접" 추출하지 않고 요소 타입을 추론할 수 있습니다.
182+
183+
```ts twoslash
184+
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
185+
```
186+
187+
여기 "참"값 분기에서 `T`의 요소 타입을 어떻게 제시할 필요 없이, `infer` 키워드를 새 제네릭 타입 변수 `Item`에 선언적으로 사용했습니다.
188+
이 방식은 관심 있는 타입의 구조를 깊게 분석하지 않아도 되도록 만들어줍니다.
189+
190+
`infer` 키워드를 사용해서 유용한 헬퍼 타입 별칭을 사용할 수 있습니다.
191+
예를 들어 함수 타입에서 리턴 타입을 추출하는 간단한 케이스를 살펴보겠습니다.
192+
193+
```ts twoslash
194+
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
195+
? Return
196+
: never;
197+
198+
type Num = GetReturnType<() => number>;
199+
// ^?
200+
201+
type Str = GetReturnType<(x: string) => string>;
202+
// ^?
203+
204+
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
205+
// ^?
206+
```
207+
208+
여러 호출 시그니처 (오버로트 함수 타입 같이)를 가진 타입을 추론할 때, _마지막_ 시그니처 (아마, 모든 케이스에 허용되는)로 추론하게 됩니다. 인자 타입의 목록에 기반해서 오버로드들을 처리할 수는 없습니다.
209+
210+
```ts twoslash
211+
declare function stringOrNum(x: string): number;
212+
declare function stringOrNum(x: number): string;
213+
declare function stringOrNum(x: string | number): string | number;
214+
215+
type T1 = ReturnType<typeof stringOrNum>;
216+
// ^?
217+
```
218+
219+
## 분산적인 조건부 타입
220+
221+
제네릭 타입 위에서 조건부 타입은 유니언 타입을 만나면 _분산적으로_ 동작합니다.
222+
예를 들어 다음을 보겠습니다.
223+
224+
```ts twoslash
225+
type ToArray<Type> = Type extends any ? Type[] : never;
226+
```
227+
228+
`ToArray`에 유니언 타입을 넘기면 조건부 타입은 유니언의 각 멤버에 적용됩니다.
229+
230+
```ts twoslash
231+
type ToArray<Type> = Type extends any ? Type[] : never;
232+
233+
type StrArrOrNumArr = ToArray<string | number>;
234+
// ^?
235+
```
236+
237+
`StrArrOrNumArr`이 동작하는 방식은 다음과 같습니다.
238+
239+
```ts twoslash
240+
type StrArrOrNumArr =
241+
// ---cut---
242+
string | number;
243+
```
244+
245+
유니언의 각 멤버 타입은 효율적으로 매핑됩니다.
246+
247+
```ts twoslash
248+
type ToArray<Type> = Type extends any ? Type[] : never;
249+
type StrArrOrNumArr =
250+
// ---cut---
251+
ToArray<string> | ToArray<number>;
252+
```
253+
254+
그리고 다음과 같이 결과가 나옵니다.
255+
256+
```ts twoslash
257+
type StrArrOrNumArr =
258+
// ---cut---
259+
string[] | number[];
260+
```
261+
262+
일반적으로 분산성이 원하는 동작입니다. 이러한 동작을 방지하려면 `extends`키워드의 양 옆을 대괄호로 감싸면 됩니다.
263+
264+
```ts twoslash
265+
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
266+
267+
// 'StrArrOrNumArr' is no longer a union.
268+
type StrArrOrNumArr = ToArrayNonDist<string | number>;
269+
// ^?
270+
```

0 commit comments

Comments
 (0)