likes
comments
collection
share

Typescript之infer 关键字

作者站长头像
站长
· 阅读数 12

在讲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 (...argsany) => any> = T extends (...argsany) => infer R ? R : any;

这就是ReturnType的原理实现,是不是有种恍然大悟的感觉,就是这么简单。


type Func = () => string;
type Test = ReturnType<Func>;
// Test = string

官网ReturnType介绍

使用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有了基本理解,接下来我们来看一个使用小技巧

tupleunion,如:[string, number] -> string | number

解答之前,我们先了解一下tuple类型在一定条件下是如何赋值给数组类型:


type TTuple = [stringnumber];
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 = [stringnumber];
type ToUnion = ElementOf<TTuple>; // string | number

看到这里还记得上面的Flatten吗?


type Flatten<T> = T extends Array<any> ? T[number] : T;
// 注意 T[number]

当然tupleunion,也可以采用这种方式:


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<TU>(action: Promise<T>): Promise<Action<U>>;
  syncMethod<TU>(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 (actionPromise<infer T>) => Promise<Action<infer U>>
  ? <T, U>(action: T) => Action<U>
  : F extends (actionAction<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.接下来就是要把PickFuncPropTransitionFunc组合起来,实现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>;
// }

参考资料: