infer 表示在 extends 条件语句中待推断的类型变量。
举个简单的例子:
type ParamsType<T> = T extends (params: infer P) => any ? P : never;
上面的代码 T extends (params: infer P) => any 中, infer P 表示待推断的参数。
解释:如果 T 的类型约束是(能赋值给)(params: infer P) ,则返回参数中推断的 P ,否则就是 never。
示例:
type User = {
name: string;
age: number;
}
type PropsFunc = (user: User) => void;
type user = ParamsType<PropsFunc>
// type user = { name: string; age: number; }
内置类型
ReturnType
用于获取函数的返回值。
源码:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
参数 T 类型约束为函数,然后推断函数返回值类型 infer R 。
用法:
type User = {
name: string;
age: number;
}
type PropsFunc = () => User;
type user = ReturnType<PropsFunc>
// type user = { name: string; age: number; }
ConstructorParameters
用于获取构造函数中的参数:
源码:
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
参数 T 类型约束为可实例化的构造函数,在构造函数 args 中我们推断类型是 infer P ,如果类型赋值成功返回推断的类型 P 。
深入了解
目前为止相信已经基本了解 infer 的特性,接下来我们来看看它的其他骚操作。
tuple 转 union (元组转联合)
解答之前,我们需要了解 tuple 类型在一定条件下,是可以赋值给数组类型;
type TTuple = [string, number];
type TArray = Array<string | number>;
type Res = TTuple extends TArray ? true : false; // true
type ResO = TArray extends TTuple ? true : false; // false
这里我们配合 infer 就很容易实现:
export type TupleToUnion<T extends any[]> = T extends Array<infer U> ? U : never;
参数 T 的类型约束为一个任意值的数组:
type Demo1 = TupleToUnion<[string, number]>
// type Demo1 = string | number;
type Demo2 = TupleToUnion<[{age: number}, {name: string}]>
// type Demo2 = {age: number} | {name: string};
union 转 intersection (联合转交集)
这个就要麻烦一点了,需要 infer 配合 【Distributive conditional types(分配条件类型)】使用。
分配条件类型的官方解释如下:
被选中类型作为外露类型参数的条件类型称为分布式条件类型。分布条件类型在实例化期间自动分布在联合类型上。
例如:T extends U ? X : Y , 其中 T 的类型是 A | B | C ,会被解析为
(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
type TypeName<T> = T extends string
? "string"
: T extends number
? "number"
: T extends boolean
? "boolean"
: T extends undefined
? "undefined"
: T extends Function
? "function"
: "object";
type T0 = TypeName<string>; // "string"
type T1 = TypeName<"a">; // "string"
type T2 = TypeName<true>; // "boolean"
type T3 = TypeName<() => void>; // "function"
type T4 = TypeName<string[]>; // "object"
type T10 = TypeName<string | (() => void)>; // "string" | "function"
type T12 = TypeName<string | string[] | undefined>; // "string" | "object" | "undefined"
type T11 = TypeName<string[] | number[]>; // "object"
有了这个例子,我们需要再变通一下,把联合类型转换为交集类型。
继续往下看:
条件类型中的类型推断
【Type inference in conditional types】(下面3个示例是官网的)
在条件类型的extends子句中,现在可以使用 infer 声明来引入要推断的类型变量。这样推断的类型变量可以在条件类型的true分支中引用。同一类型变量可以有多个 infer 位置。
可以嵌套条件类型以形成一个模式匹配序列,并按以下顺序求值:
type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U>
? U
: T;
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Promise<string>[]>; // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
下面的例子演示了在协变位置上的同一类型变量的多个候选变量是如何导致推断联合类型的:
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: string) => void; b: (x: number) => void }>; // string & number
注意了注意了,圈起来要考:
我测试的结果是 type T21 = never;
因为代码是官方的所以我没改,那为什么会是这个结果呢?
我是 这里 找到了答案,因为ts3.6+优化了这个结果。
原因是: 一个值不可能同时是string和number。
综合以上知识,我在 utility-types 上,找到了一个联合转交集的方法:
export type UnionToIntersection<T> = (T extends any ? (arg: T) => void : never)
extends (arg: infer V) => void
? V
: never
示例:
type T1 = UnionToIntersection<{age: number} | {name: string}>
// type T1 = {age: number} & {name: string};
第一部分 T extends any,这个什么鬼,为啥要判断 extends any? 【来看这里】
解析步骤:
1、(T extends any ? (arg: T) => void : never) 会把 union 参数拆分成
{age: number} extends any ? (arg: {age: number}) => void : never |
{name: string} extends any ? (arg: {name: string}) => void : never
即我们得到:
(arg: {age: number}) => void | (arg: {name: string}) => void
2、((arg: {age: number}) => void) | ((arg: {name: string}) => void) extends (arg: infer V) => void ? V : never
根据上文我们推断出
V = {age: number} & {name: string}
虽然得到了结果,但是会不会一脸懵逼???
为什么是 {age: number} & {name: string} 而不是 {age: number} | {name: string}
正常思维理解 A | B extends 的结果应该是 A | B 呀,为什么是 A & B 呢???
带着疑问我们继续讲解。
A|B 得到 A&B
这里还得感谢 这篇文章 。
根据这篇文件,我们再进行一个具体的分析:
第一部分是:(T extends any ? (arg: T) => void : never)
第二部分是:extends (arg: infer V) => void ? V : never
第一部分的结果上面已经说过了,我们在这里给简化一下。
(arg: number) => void | (arg: string) => void
第二部分 extends (arg: infer V) => void ? V : never 意思是如果左边的是一个函数就把它的参数拿出来返回。
那么问题来了 (arg: number) => void | (arg: string) => void 返回 number & string ???
因为函数参数是逆变的,我们假设有一个变量能赋值给 (arg: number) => void | (arg: string) => void,那么这个变量的类型只能是 number & string 而不是 number | string
A extends B,意味着所有 B 都可以无条件被 A 替换。
看到这里,会有同学更困惑了, 这篇文章的上面 可不是这样说的啊???
一脸懵逼!!!o((⊙﹏⊙))o
union 转 tuple (联合转元组)
这种骚操作看 union To tuple。