Skip to content

Commit 9425a4f

Browse files
committed
docs(zh-Hans): translate /handbook-v2/Type Manipulation/Conditional Types.md to Chinese
1 parent aa85ac1 commit 9425a4f

File tree

1 file changed

+275
-0
lines changed

1 file changed

+275
-0
lines changed
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
---
2+
title: 条件类型
3+
layout: docs
4+
permalink: /zh/docs/handbook/2/conditional-types.html
5+
oneline: "Create types which act like if statements in the type system."
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 中的条件表达式(`条件 ? true 表达式 : false 表达式`):
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` 左边的类型可以赋值给右边的类型时,你将获得第一个分支("true" 分支)中的类型;否则你将获得后一个分支("false" 分支)中的类型。
40+
41+
从上面的例子中,条件类型可能看起来不会立即有用 - 我们可以告诉自己是否 `Dog extends Animal` 并选择 `number``string`
42+
但是条件类型的威力来自于将它们与泛型一起使用。
43+
44+
让我们以下面的 `createLabel` 函数为例:
45+
46+
```ts twoslash
47+
interface IdLabel {
48+
id: number /* 一些字段 */;
49+
}
50+
interface NameLabel {
51+
name: string /* 其它字段 */;
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. 我们必须创建三个重载:一种用于我们 _确定_ 类型时的每种情况(一个用于 `string`,一个用于 `number`),一个用于最一般的情况(接受一个 `string | number`)。对于 `createLabel` 可以处理的每个新类型,重载的数量都会呈指数增长。
66+
67+
相反,我们可以将该逻辑编码为条件类型:
68+
69+
```ts twoslash
70+
interface IdLabel {
71+
id: number /* 一些字段 */;
72+
}
73+
interface NameLabel {
74+
name: string /* 其它字段 */;
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 /* 一些字段 */;
87+
}
88+
interface NameLabel {
89+
name: string /* 其它字段 */;
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+
就像使用类型守卫缩小范围可以给我们提供更具体的类型一样,条件类型的 true 分支将根据我们检查的类型进一步约束泛型。
113+
114+
让我们来看看下面的例子:
115+
116+
```ts twoslash
117+
// @errors: 2536
118+
type MessageOf<T> = T["message"];
119+
```
120+
121+
在本例中,TypeScript 错误是因为 `T` 不知道有一个名为 `message` 的属性。
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+
interface Dog {
132+
bark(): void;
133+
}
134+
135+
type EmailMessageContents = MessageOf<Email>;
136+
// ^?
137+
```
138+
139+
然而,如果我们希望 `MessageOf` 采用任何类型,并且在 `message` 属性不可用的情况下缺省为 `never` 之类的类型,该怎么办呢?
140+
我们可以通过移出约束并引入条件类型来实现这一点:
141+
142+
```ts twoslash
143+
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
144+
145+
interface Email {
146+
message: string;
147+
}
148+
149+
interface Dog {
150+
bark(): void;
151+
}
152+
153+
type EmailMessageContents = MessageOf<Email>;
154+
// ^?
155+
156+
type DogMessageContents = MessageOf<Dog>;
157+
// ^?
158+
```
159+
160+
在 true 分支中,TypeScript 知道 `T` __ 有一个 `message` 属性。
161+
162+
作为另一个示例,我们还可以编写一个名为 `Flatten` 的类型,它将数组类型扁平为它们的元素类型,但在其他情况下不会处理它们:
163+
164+
```ts twoslash
165+
type Flatten<T> = T extends any[] ? T[number] : T;
166+
167+
// Extracts out the element type.
168+
type Str = Flatten<string[]>;
169+
// ^?
170+
171+
// Leaves the type alone.
172+
type Num = Flatten<number>;
173+
// ^?
174+
```
175+
176+
`Flatten` 被赋予数组类型时,它使用带 `number` 的索引访问来提取 `string[]` 的元素类型。
177+
否则,它只返回给定的类型。
178+
179+
### 在条件类型中推断
180+
181+
我们只是发现自己使用条件类型来应用约束,然后提取出类型。
182+
这最终成为一种非常常见的操作,条件类型使其变得更容易。
183+
184+
条件类型为我们提供了一种使用 `infer` 关键字从 true 分支中与之进行比较的类型中进行推断的方法。
185+
例如,我们可以在 `Flatten` 中推断元素类型,而不是使用索引访问类型“手动”提取它:
186+
187+
```ts twoslash
188+
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
189+
```
190+
191+
在这里,我们使用 `infer` 关键字以声明方式引入一个名为 `Item` 的新泛型类型变量,而不是指定如何在 true 分支中检索元素类型 `T`
192+
这使我们不必考虑如何挖掘和探索我们感兴趣的类型的结构。
193+
194+
我们可以使用 `infer` 关键字编写一些有用的助手类型别名。
195+
例如,对于简单的情况,我们可以从函数类型中提取返回类型:
196+
197+
```ts twoslash
198+
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
199+
? Return
200+
: never;
201+
202+
type Num = GetReturnType<() => number>;
203+
// ^?
204+
205+
type Str = GetReturnType<(x: string) => string>;
206+
// ^?
207+
208+
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
209+
// ^?
210+
```
211+
212+
当从具有多个调用签名的类型(如重载函数的类型)进行推断时,将从 _最后一个_ 签名进行推断(这大概是最允许的捕获所有的情况)。无法基于参数类型列表执行重载解析。
213+
214+
```ts twoslash
215+
declare function stringOrNum(x: string): number;
216+
declare function stringOrNum(x: number): string;
217+
declare function stringOrNum(x: string | number): string | number;
218+
219+
type T1 = ReturnType<typeof stringOrNum>;
220+
// ^?
221+
```
222+
223+
## 分配条件类型
224+
225+
当条件类型作用于泛型类型时,它们在给定联合类型时成为 _分配类型_
226+
以下面的例子为例:
227+
228+
```ts twoslash
229+
type ToArray<Type> = Type extends any ? Type[] : never;
230+
```
231+
232+
如果我们将联合类型插入 `ToArray`,则条件类型将应用于该联合类型的每个成员。
233+
234+
```ts twoslash
235+
type ToArray<Type> = Type extends any ? Type[] : never;
236+
237+
type StrArrOrNumArr = ToArray<string | number>;
238+
// ^?
239+
```
240+
241+
这里发生的情况是 `StrOrNumArray` 分布在以下位置:
242+
243+
```ts twoslash
244+
type StrArrOrNumArr =
245+
// ---cut---
246+
string | number;
247+
```
248+
249+
并在联合类型的每个成员类型上映射到有效的内容:
250+
251+
```ts twoslash
252+
type ToArray<Type> = Type extends any ? Type[] : never;
253+
type StrArrOrNumArr =
254+
// ---cut---
255+
ToArray<string> | ToArray<number>;
256+
```
257+
258+
所以我们只剩下:
259+
260+
```ts twoslash
261+
type StrArrOrNumArr =
262+
// ---cut---
263+
string[] | number[];
264+
```
265+
266+
通常,分布性是所需的行为。
267+
要避免这种行为,可以用方括号括起 `extends` 关键字的两边。
268+
269+
```ts twoslash
270+
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
271+
272+
// 'StrOrNumArr' 不再是一个联合类型
273+
type StrOrNumArr = ToArrayNonDist<string | number>;
274+
// ^?
275+
```

0 commit comments

Comments
 (0)