ts-extends、infer

72 阅读3分钟

1. 协变与逆变

参考上一篇中的类型兼容

  • 协变:子类型赋值给父类型
  • 逆变:父类型赋值给子类型。
    • 函数赋值时,参数类型的父类型可以赋值给子类型。因为最终调用函数时子类型的参数访问父类型声明的属性与方法是安全的。

2. extends

2.1 接口继承

参考接口继承

2.2 泛型约束

interface Length {
    length: number;
}
function printLength<T extends Length>(a: T): T {
    console.log(a.length);
    return a;
}

T extends Length表示泛型T必须符合接口Length,即必须有length属性,且其值类型为number。

2.3 条件类型

形式类似于三目运算符:SomeType extends OtherType ? TrueType : FalseType;

extends左侧类型可以赋值给右侧的类型,则返回 TrueType,否则返回 FalseType;

interface Animal {
    name:string;
    eat:()=>void
}

interface Cat extends Animal {
    mew:()=>void
}

type Example1 = Cat extends Animal ? number : string; // type Example1 = number
type Example2 = Animal extends Cat ? number : string; // type Example2 = string

CatAnimal的子类型,可以赋值给Animal,反过来不行,所以Example1最终的类型为numberExample2string.

demo

TS4.1版本开始支持模板字面量类型。

在模板字符串中使用extends

type StartsWith<Str extends string, Prefix extends string> =

Str extends `${Prefix}${string}` ? true : false;

type StartsWithResult = StartsWith<'pass', 'p'>; // true
type StartsWithResult2 = StartsWith<'fail', 'p'>; // false

泛型传入的类型为模版字面量类型,如pass,也符合泛型约束,而在条件类型的判断部分来判断Str类型是否满足以Prefix作为开头。

模板demo

3. 分配条件类型(Distributive Conditional Types)

使用条件类型时,如果左侧的参数为泛型类型,且传入的该参数为联合类型,条件类型将会作用到该联合类型的每一个成员。

类似于使用分配率,将联合类型拆分为单项,分别代入条件类型,最后将各单项的代入得到的结果联合作为最终结果。

type StrOrNum = string | number

type Result1 = StrOrNum extends string ? string : number

type Generic<T> = T extends string ? string : number

// 使用泛型,泛型参数为联合类型
type Result2 = Generic<StrOrNum> // string | number

// 等价于
type Type1 = Generic<string>
type Type2 = Generic<number>
type Result3 = Type1 | Type2 // string | number

若要避免这种行为,可以用方括号将 extends 关键字的每一侧括起来。

// 不希望使用分配条件类型
type Generic2<T> = [T] extends [string] ? string : number;

type Result4 = Generic2<StrOrNum> // number

demo

4. Infer

在extends条件类型,还可以使用infer关键字,可以用于推断一个类型变量,但该变量只能在条件类型的true分支上使用。

type ItemType<T extends any[]>  = T extends (infer R)[]? R : never;

type temp1 = ItemType<number[]> // number
type temp2 = ItemType<string[]> // string
type temp3 = ItemType<[1,2,3]> // 1 | 2 | 3

ItemType限制泛型为数组,并用 infer来推断数组成员的类型。

可在一个条件类型表达式中推断多个类型变量

type FuncType = (a: string, b: number) => boolean

type FuncValueAndReturn<T extends function> = 
T extends (a: infer A, b: infer B, ...args: any[]) => infer C ? [A, B, C]: never

type result = FuncValueAndReturn<FuncType> // [string, number, boolean]

FuncValueAndReturn限制泛型为函数类型,并提取前两个参数及返回值类型,组合成数组返回。

在一个条件类型表达式中多处使用同一个类型变量

  1. 协变

在协变位置上,同一个类型变量的多个候选类型会被推断为联合类型

type ItemType2<T extends any[]> = T extends [infer R, infer R, ...any[]] ? R : never;

type temp2 = ItemType2<[number, string]> // string | number
type temp2 = ItemType2<[1, 2, 3]> // 1 | 2
  1. 逆变

在逆变位置上,同一个类型变量的多个候选类型会被推断为交叉类型

type FuncType = (a: string, b: number) => boolean
type FuncValueAndReturn2<T extends function> = 
T extends (a: infer A, b: infer A) => infer C ? [A, C] : never;

type result2 = FuncValueAndReturn2<FuncType> // [string & number, boolean]

在模板字符串中使用infer

type ReplaceStr<Str extends string, From extends string, To extends string> 
 = Str extends `${infer Prefix}${From}${infer Suffix}` ? `${Prefix}${To}${Suffix}` : Str;

type ReplaceResult = ReplaceStr<'javascript','java','type'>

ReplaceStr实现了将Str中的From替换为To

demo

模板demo