typescript高级:infer关键字

3,181 阅读3分钟

本文部分实例来源于:深入理解typescript

infer的基础作用

一个函数定义,A->B,我们可能需要细化成出,函数的参数类型 A或者返回值类型B,这个关键字就是完成这个用途。

interface User {
  name: string;
  age: number;
}
type Func = (user: User) => void;
type ParamType<T> = T extends (param: infer P) => any ? P : T;
type Test = ParamType<Func>; // Test = User
type s = ParamType<string>; // string

在这个条件语句 T extends (param: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。

整句表示为:如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P,否则返回为 T

它也可以表示返回值

type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any;
type Func2 = () => User;
type Param = ParamType<Func>; // Param = User

简单情况下,P就是输入函数参数或者返回值对应的类型。

infer与逆变和逆变

详细分析关键字之前,先回顾一下协变和逆变

约定A → B 指的是以 A 为参数类型,以 B 为返回值类型的函数类型。

先有如下定义

class A { }
class B extends A { }
class C extends B { }
function f(g: (arg:B) => B){
    
}

B继承于AC继承于B, 有个函数f,要求输入一个参数g的类型是 B -> B

当我们输入g:arg->back时,argback需要满足以下条件才能通过检验 ,

  • argB及其超类(此处是A)
  • backB及其子类(此处是C)

这种情况我们称,函数的返回值类型是协变的,而参数类型是逆变的。

有关分析可以看这篇文章。

提出一个问题,以上面的示例来说,f要求g的类型时, g extends B -> B 还是 B -> B extend g ?

参数需要满足要求,显然是前者。设T:a -> b, T extends P -> K 为真,

那么,Pa是逆变的,Kb是协变的

我们看一个经典且常用的例子,联合类型转交叉类型。

我们首先需要知道extends的distributive(分配律)特性,简单来说,就是

type F<T> = T extends U ? X : Y
type union_type = A | B | C;
type a = F<union_type>;
//那么a的结果为 A extends U ? X :Y | B extends U ? X :Y | C extends U ? X : Y

更加详细的特性,可以看这篇文章:深入typescript类型系统(二): 泛型和类型元编程

然后我们写出这个函数

type unionDistribute<U> = U extends any ? (_: U) => void : never
type a = unionDistribute<T1 | T2>
// (_:T1 => void) | (_:T2) => void

我们再利用在逆变位置上(此处为参数位置), 同一类型变量的多个候选类型将会被推断为交叉类型的特性

type Bar<T> = T extends (_:infer U) => void ? U : never;
type T21 = Bar<a: (x: T1) => void | b: (x: T2) => void>; // T1 & T2

最后将这两个泛型函数复合就可以得到我们的结果

type UnionToIntersection<U> =  Bar<unionDistribute<U>>
type a1 = UnionToIntersection<{ a: number } | { b: string }>
//{a: number } & {b: string }

我们应该如何理解从
{ a: number } & { b: string }{ a: number } | { b: string } 是逆变的呢?

interface A { a: number }
interface B { b: string }
interface C extends A, B { }

以上面的示例来说,ABC继承,那么C就拥有AB的所有属性,我们知道 { a: number } & { b: string }等同于 { a: number , b: string },所以CA | B是逆变的。

再举一个简单的例子吧,UnionToIntersection<"a" | "b">的结果是"a"&"b"等价于nevernever类型是任何类型的子类型 ,也就是说never"a" | "b"是逆变的。

内置类型

typescript中也内置了很多常用类型

  • 用于提取构造函数中参数(实例)类型:

    一个构造函数可以使用 new 来实例化,因此它的类型通常表示如下:

    type Constructor = new (...args: any[]) => any;
    

    infer 用于构造函数类型中,可用于参数位置 new (...args: infer P) => any; 和返回值位置 new (...args: any[]) => infer P;

    因此就内置如下两个映射类型:

    // 获取构造函数参数类型
    type ConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any
      ? P
      : never;
    
    // 获取返回实例类型
    type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;
    
    class TestClass {
      constructor(public name: string, public age: number) {}
    }
    
    type Params = ConstructorParameters<typeof TestClass>; // [string, number]
    
    type Instance = InstanceType<typeof TestClass>; // TestClass
    

typescript真的太难了/(ㄒoㄒ)/~~ 不研究它了哼