Typescript之infer 关键字
在讲infer
关键字之前,先看一下不用infer
的时候,实现一个条件类型推断的例子。
条件类型约束示例
编写一个类型Flatten
,将数组类型展平为它们的元素类型,否则就将其保留下来:
type Flatten<T> = T extends Array<any> ? T[number] : T;
type Str = Flatten<Array<string>>;
// type Str = string
type Num = Flatten<number>;
// type Num = number
当Flatten
给定的是Array<string>
时,它使用索引number
来获取Array的元素类型。否则,返回给定的类型。
内置类型
不知道你在写TS代码的时候,又没用过ReturnType
这个内置类型。当你在使用它的时候有没有好奇它内部实现。
❝Conditional types provide us with a way to infer from types we compare against in the true branch using the infer keyword.
❞
❝翻译过来大致是:条件类型为我们提供了一种使用
❞infer
关键字从true分支中比较的类型进行推断的方法。
// 用于提取函数类型的返回值类型
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
这就是ReturnType
的原理实现,是不是有种恍然大悟的感觉,就是这么简单。
type Func = () => string;
type Test = ReturnType<Func>;
// Test = string
使用infer推断
条件类型为我们提供了一种使用infer
关键字从真实分支中进行比较的类型推断的方法。例如,我们可以推断出元素类型,Flatten
不再是使用索引访问类型“手动”获取元素类型:
type Flatten<T> = T extends Array<infer U> ? U : T;
// Flatten<string> <=> string
// Flatten<Array<number>> <=> number
我们使用infer
关键字声明性地引入了一个名为的新泛型类型变量infer U
表示待推断的函数参数
整句的意思为:如果 T
能 赋值给 Array<infer U>
,则结果是Array<infer U>
里的类型U
,否则返回T
.
使用例子
看到这里,相信你也对infer
有了基本理解,接下来我们来看一个使用小技巧
「tuple」 转 「union」,如:[string, number] -> string | number
解答之前,我们先了解一下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
时,就很容易做到:
type ElementOf<T> = T extends Array<infer E> ? E : never
type TTuple = [string, number];
type ToUnion = ElementOf<TTuple>; // string | number
看到这里还记得上面的Flatten
吗?
type Flatten<T> = T extends Array<any> ? T[number] : T;
// 注意 T[number]
当然「tuple」 转 「union」,也可以采用这种方式:
type TTuple = [string, number];
type Res = TTuple[number]; // string | number
是不是感觉这波操作很cool
。
一道面试题
题目内容如下:
interface Action<T> {
payload?: T;
type: string;
}
// 假设有Modle这样一个interface
interface Module {
count: number;
message: string;
asyncMethod<T, U>(action: Promise<T>): Promise<Action<U>>;
syncMethod<T, U>(action: Action<T>): Action<U>;
}
// 实现type Connect
// 保留属性为函数类型,其余的摒弃掉
// 把函数类型转化为<T, U>(args: T) => Action<U>
type Connect<T> = /** 你需要实现的逻辑 */
type Result = Connect<Module>;
// Result = {
// asyncMethod<T, U>(input: T): Action<U>;
// syncMethod<T, U>(action: T): Action<U>;
// }
这里主要考察了两点
- 挑选出函数
- 条件类型+本文提及infer
1.先实现一个类型可以筛选出为函数类型的属性。
type PickFuncProp<T> = {
[P in keyof T]: T[P] extends Function ? P : never;
}[keyof T];
// PickFuncProp<Module> <=> 'asyncMethod'|'syncMethod'
2.使用infer实现函数的转换。
type TransitionFunc<F> = F extends (action: Promise<infer T>) => Promise<Action<infer U>>
? <T, U>(action: T) => Action<U>
: F extends (action: Action<infer T>) => Action<infer U>
? <T,U>(action: T) => Action<U>
: F;
type syncMethod<T, U> = (action: Action<T>) => Action<U>;
type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>;
// TransitionFunc<syncMethod> <=> <T, U>(action: T) => Action<U>;
// TransitionFunc<asyncMethod> <=> <T, U>(action: T) => Action<U>;
3.接下来就是要把PickFuncProp
和TransitionFunc
组合起来,实现Connect
的方法.
type Connect<T> = {
[P in PickFuncProp<T>]: TransitionFunc<T[P]>;
};
type Result = Connect(Module);
// Result = {
// asyncMethod: <T, U>(action: T) => Action<U>;
// syncMethod: <T, U>(action: T) => Action<U>;
// }
参考资料:
转载自:https://juejin.cn/post/6907908365930725389