条件类型是ts中非常强大的功能,形如:T extends U ? X : Y
,表示如果T
是U
的子类,则返回类型为X
,否则为Y
。和三元表达式相似。
我们可以定义一个泛型类型IsString
,根据T
的类型,判断返回的具体类型是true
还是false
:
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
分布式条件类型
条件类型还有一个特性:分布式条件类型。在结合联合类型使用时(只针对extends
左边的联合类型),分布式条件类型会被自动分发成联合类型。
分布式条件类型 | 等价于 |
---|---|
string extends T ? A : B |
string extends T ? A : B |
(string | number) extends T ? A : B |
(string extends T ? A : B) | (number extends T ? A : B) |
(string | number | boolean) extends T ? A : B |
(string extends T ? A : B) | (number extends T ? A : B) | (boolean extends T ? A : B) |
假如我们有一个泛型类型Without<T, U>
,用于筛选在T
中但不在U
中的类型:
type Without<T, U> = T extends U ? never : T;
我们可这样使用:
type A = Without<boolean | number | string, boolean>; // number | string
得到的结果是number | string
。让我们一步一步分析:
- 给泛型指定具体类型:
type A = Without<boolean | number | string, boolean>;
- 自动分发为联合类型:
type A = Without<boolean, boolean>
| Without<number, boolean>
| Without<string, boolean>
- 替换为具体的定义:
type A = (boolean extends boolean ? never : boolean)
| (number extends boolean ? never : number)
| (string extends boolean ? never : string)
- 计算结果:
type A = never
| number
| string
- 简化结果,过滤
never
:
type A = number | string
在官方文档中提到,分布式条件类型是有前提的。条件类型中待检查的类型(即extends
左边的类型)必须是裸类型(naked type parameter
)。即没有被诸如数组,元组或者函数包裹。
// naked type
type NakedType<T> = T extends boolean ? "yes" : "no";
type DistributedUsage = NakedType<number | boolean>; // "yes" | "no"
// wrapped type
type WrappedType<T> = Array<T> extends Array<boolean> ? "yes" : "no";
type NonDistributedUsage = WrappedType<number | boolean>; // "no"
infer
条件类型还可以在条件判断中声明泛型类型。通过使用infer
关键字。
假如我们希望获取一个函数的返回值:
type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
type Foo = FunctionReturnType<() => void>; // void
type Bar = FunctionReturnType<(name: string) => string>; // string
type Buz = FunctionReturnType<(name: string, other: string) => boolean>; // boolean
infer R
就是在条件判断中声明的新的泛型,它会根据实际传入的泛型类型推断出该泛型的具体类型。
多个相同泛型变量在协变的地方会被推断为联合类型,在逆变的地方会被推断为交叉类型。协变和逆变章节对协变逆变有具体讲解。
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
type T10 = Foo<{ a: string; b: string }>; // string
type T11 = Foo<{ a: string; b: number }>; // string | number
type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
? U
: never;
type T20 = Bar<{ a: (x: string) => void; b: (x: string) => void }>; // string
type T21 = Bar<{
a: (x: { name: string }) => void;
b: (x: { age: number }) => void;
}>; // { name: string;} & { age: number;}
如果我们对函数重载使用infer
,则会以最后一个重载类型定义为准:
declare function foo(x: string): number;
declare function foo(x: number): string;
declare function foo(x: string | number): string | number;
type T30 = ReturnType<typeof foo>; // string | number
ReturnType
是ts内置的工具类型,和本节中的FunctionReturnType
功能一致。