Skip to content

Commit 3500a1c

Browse files
committed
conditional type
1 parent 77f3076 commit 3500a1c

1 file changed

Lines changed: 275 additions & 0 deletions

File tree

doc/handbook/Advanced Types.md

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,3 +925,278 @@ let originalProps = unproxify(proxyProps);
925925

926926
注意这个拆包推断只适用于同态的映射类型。
927927
如果映射类型不是同态的,那么需要给拆包函数一个明确的类型参数。
928+
929+
## 有条件类型
930+
931+
TypeScript 2.8引入了*有条件类型*,它能够表示非统一的类型。
932+
有条件的类型会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一:
933+
934+
```ts
935+
T extends U ? X : Y
936+
```
937+
938+
上面的类型意思是,若`T`能够赋值给`U`,那么类型是`X`,否则为`Y`
939+
940+
有条件的类型`T extends U ? X : Y`或者*解析*`X`,或者*解析*`Y`,再或者*延迟*解析,因为它可能依赖一个或多个类型变量。
941+
`T``U`包含类型参数,那么是否解析为`X``Y`或推迟,取决于类型系统是否有足够的信息来确定`T`总是可以赋值给`U`
942+
943+
下面是一些类型可以被立即解析的例子:
944+
945+
```ts
946+
declare function f<T extends boolean>(x: T): T extends true ? string : number;
947+
948+
// Type is 'string | number
949+
let x = f(Math.random() < 0.5)
950+
951+
```
952+
953+
另外一个例子涉及`TypeName`类型别名,它使用了嵌套了有条件类型:
954+
955+
```ts
956+
type TypeName<T> =
957+
T extends string ? "string" :
958+
T extends number ? "number" :
959+
T extends boolean ? "boolean" :
960+
T extends undefined ? "undefined" :
961+
T extends Function ? "function" :
962+
"object";
963+
964+
type T0 = TypeName<string>; // "string"
965+
type T1 = TypeName<"a">; // "string"
966+
type T2 = TypeName<true>; // "boolean"
967+
type T3 = TypeName<() => void>; // "function"
968+
type T4 = TypeName<string[]>; // "object"
969+
```
970+
971+
下面是一个有条件类型被推迟解析的例子:
972+
973+
```ts
974+
interface Foo {
975+
propA: boolean;
976+
propB: boolean;
977+
}
978+
979+
declare function f<T>(x: T): T extends Foo ? string : number;
980+
981+
function foo<U>(x: U) {
982+
// Has type 'U extends Foo ? string : number'
983+
let a = f(x);
984+
985+
// This assignment is allowed though!
986+
let b: string | number = a;
987+
}
988+
```
989+
990+
这里,`a`变量含有未确定的有条件类型。
991+
当有另一段代码调用`foo`,它会用其它类型替换`U`,TypeScript将重新计算有条件类型,决定它是否可以选择一个分支。
992+
993+
与此同时,我们可以将有条件类型赋值给其它类型,只要有条件类型的每个分支都可以赋值给目标类型。
994+
因此在我们的例子里,我们可以将`U extends Foo ? string : number`赋值给`string | number`,因为不管这个有条件类型最终结果是什么,它只能是`string``number`
995+
996+
### 分布式有条件类型
997+
998+
如果有条件类型里待检查的类型是`naked type parameter`,那么它也被称为“分布式有条件类型”。
999+
分布式有条件类型在实例化时会自动分发成联合类型。
1000+
例如,实例化`T extends U ? X : Y``T`的类型为`A | B | C`,会被解析为`(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)`
1001+
1002+
#### 例子
1003+
1004+
```ts
1005+
type T10 = TypeName<string | (() => void)>; // "string" | "function"
1006+
type T12 = TypeName<string | string[] | undefined>; // "string" | "object" | "undefined"
1007+
type T11 = TypeName<string[] | number[]>; // "object"
1008+
```
1009+
1010+
`T extends U ? X : Y`的实例化里,对`T`的引用被解析为联合类型的一部分(比如,`T`指向某一单个部分,在有条件类型分布到联合类型之后)。
1011+
此外,在`X`内对`T`的引用有一个附加的类型参数约束`U`(例如,`T`被当成在`X`内可赋值给`U`)。
1012+
1013+
#### 例子
1014+
1015+
```ts
1016+
type BoxedValue<T> = { value: T };
1017+
type BoxedArray<T> = { array: T[] };
1018+
type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>;
1019+
1020+
type T20 = Boxed<string>; // BoxedValue<string>;
1021+
type T21 = Boxed<number[]>; // BoxedArray<number>;
1022+
type T22 = Boxed<string | number[]>; // BoxedValue<string> | BoxedArray<number>;
1023+
```
1024+
1025+
注意在`Boxed<T>``true`分支里,`T`有个额外的约束`any[]`,因此它适用于`T[number]`数组元素类型。同时也注意一下有条件类型是如何分布成联合类型的。
1026+
1027+
有条件类型的分布式的属性可以方便地用来*过滤*联合类型:
1028+
1029+
```ts
1030+
type Diff<T, U> = T extends U ? never : T; // Remove types from T that are assignable to U
1031+
type Filter<T, U> = T extends U ? T : never; // Remove types from T that are not assignable to U
1032+
1033+
type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
1034+
type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
1035+
type T32 = Diff<string | number | (() => void), Function>; // string | number
1036+
type T33 = Filter<string | number | (() => void), Function>; // () => void
1037+
1038+
type NonNullable<T> = Diff<T, null | undefined>; // Remove null and undefined from T
1039+
1040+
type T34 = NonNullable<string | number | undefined>; // string | number
1041+
type T35 = NonNullable<string | string[] | null | undefined>; // string | string[]
1042+
1043+
function f1<T>(x: T, y: NonNullable<T>) {
1044+
x = y; // Ok
1045+
y = x; // Error
1046+
}
1047+
1048+
function f2<T extends string | undefined>(x: T, y: NonNullable<T>) {
1049+
x = y; // Ok
1050+
y = x; // Error
1051+
let s1: string = x; // Error
1052+
let s2: string = y; // Ok
1053+
}
1054+
```
1055+
1056+
有条件类型与映射类型结合时特别有用:
1057+
1058+
```ts
1059+
type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
1060+
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
1061+
1062+
type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];
1063+
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
1064+
1065+
interface Part {
1066+
id: number;
1067+
name: string;
1068+
subparts: Part[];
1069+
updatePart(newName: string): void;
1070+
}
1071+
1072+
type T40 = FunctionPropertyNames<Part>; // "updatePart"
1073+
type T41 = NonFunctionPropertyNames<Part>; // "id" | "name" | "subparts"
1074+
type T42 = FunctionProperties<Part>; // { updatePart(newName: string): void }
1075+
type T43 = NonFunctionProperties<Part>; // { id: number, name: string, subparts: Part[] }
1076+
```
1077+
1078+
与联合类型和交叉类型相似,有条件类型不允许递归地引用自己。比如下面的错误。
1079+
1080+
#### 例子
1081+
1082+
```ts
1083+
type ElementType<T> = T extends any[] ? ElementType<T[number]> : T; // Error
1084+
```
1085+
1086+
### 有条件类型中的类型推断
1087+
1088+
现在在有条件类型的`extends`子语句中,允许出现`infer`声明,它会引入一个待推断的类型变量。
1089+
这个推断的类型变量可以在有条件类型的true分支中被引用。
1090+
允许出现多个同类型变量的`infer`
1091+
1092+
例如,下面代码会提取函数类型的返回值类型:
1093+
1094+
```ts
1095+
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
1096+
```
1097+
1098+
有条件类型可以嵌套来构成一系列的匹配模式,按顺序进行求值:
1099+
1100+
```ts
1101+
type Unpacked<T> =
1102+
T extends (infer U)[] ? U :
1103+
T extends (...args: any[]) => infer U ? U :
1104+
T extends Promise<infer U> ? U :
1105+
T;
1106+
1107+
type T0 = Unpacked<string>; // string
1108+
type T1 = Unpacked<string[]>; // string
1109+
type T2 = Unpacked<() => string>; // string
1110+
type T3 = Unpacked<Promise<string>>; // string
1111+
type T4 = Unpacked<Promise<string>[]>; // Promise<string>
1112+
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
1113+
```
1114+
1115+
下面的例子解释了在协变位置上,同一个类型变量的多个候选类型会被推断为联合类型:
1116+
1117+
```ts
1118+
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
1119+
type T10 = Foo<{ a: string, b: string }>; // string
1120+
type T11 = Foo<{ a: string, b: number }>; // string | number
1121+
```
1122+
1123+
相似地,在抗变位置上,同一个类型变量的多个候选类型会被推断为交叉类型:
1124+
1125+
```ts
1126+
type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
1127+
type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>; // string
1128+
type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number
1129+
```
1130+
1131+
当推断具有多个调用签名(例如函数重载类型)的类型时,用*最后*的签名(大概是最自由的包含所有情况的签名)进行推断。
1132+
无法根据参数类型列表来解析重载。
1133+
1134+
```ts
1135+
declare function foo(x: string): number;
1136+
declare function foo(x: number): string;
1137+
declare function foo(x: string | number): string | number;
1138+
type T30 = ReturnType<typeof foo>; // string | number
1139+
```
1140+
1141+
无法在正常类型参数的约束子语句中使用`infer`声明:
1142+
1143+
```ts
1144+
type ReturnType<T extends (...args: any[]) => infer R> = R; // 错误,不支持
1145+
```
1146+
1147+
但是,可以这样达到同样的效果,在约束里删掉类型变量,用有条件类型替换:
1148+
1149+
```ts
1150+
type AnyFunction = (...args: any[]) => any;
1151+
type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R ? R : any;
1152+
```
1153+
1154+
### 预定义的有条件类型
1155+
1156+
TypeScript 2.8在`lib.d.ts`里增加了一些预定义的有条件类型:
1157+
1158+
* `Exclude<T, U>` -- 从`T`中剔除可以赋值给`U`的类型。
1159+
* `Extract<T, U>` -- 提取`T`中可以赋值给`U`的类型。
1160+
* `NonNullable<T>` -- 从`T`中剔除`null``undefined`
1161+
* `ReturnType<T>` -- 获取函数返回值类型。
1162+
* `InstanceType<T>` -- 获取构造函数类型的实例类型。
1163+
1164+
#### Example
1165+
1166+
```ts
1167+
type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
1168+
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
1169+
1170+
type T02 = Exclude<string | number | (() => void), Function>; // string | number
1171+
type T03 = Extract<string | number | (() => void), Function>; // () => void
1172+
1173+
type T04 = NonNullable<string | number | undefined>; // string | number
1174+
type T05 = NonNullable<(() => string) | string[] | null | undefined>; // (() => string) | string[]
1175+
1176+
function f1(s: string) {
1177+
return { a: 1, b: s };
1178+
}
1179+
1180+
class C {
1181+
x = 0;
1182+
y = 0;
1183+
}
1184+
1185+
type T10 = ReturnType<() => string>; // string
1186+
type T11 = ReturnType<(s: string) => void>; // void
1187+
type T12 = ReturnType<(<T>() => T)>; // {}
1188+
type T13 = ReturnType<(<T extends U, U extends number[]>() => T)>; // number[]
1189+
type T14 = ReturnType<typeof f1>; // { a: number, b: string }
1190+
type T15 = ReturnType<any>; // any
1191+
type T16 = ReturnType<never>; // any
1192+
type T17 = ReturnType<string>; // Error
1193+
type T18 = ReturnType<Function>; // Error
1194+
1195+
type T20 = InstanceType<typeof C>; // C
1196+
type T21 = InstanceType<any>; // any
1197+
type T22 = InstanceType<never>; // any
1198+
type T23 = InstanceType<string>; // Error
1199+
type T24 = InstanceType<Function>; // Error
1200+
```
1201+
1202+
> 注意:`Exclude`类型是[建议的](https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458)`Diff`类型的一种实现。我们使用`Exclude`这个名字是为了避免破坏已经定义了`Diff`的代码,并且我们感觉这个名字能更好地表达类型的语义。我们没有增加`Omit<T, K>`类型,因为它可以很容易的用`Pick<T, Exclude<keyof T, K>>`来表示。

0 commit comments

Comments
 (0)