Skip to content

条件类型,分布式条件类型,infer

1.条件类型

条件类型的语法类似于我们平时常用的三元表达式:

ts
ValueA === ValueB ? Result1 : Result2;
TypeA extends TypeB ? Result1 : Result2;

条件类型绝大部分场景下会和泛型一起使用,泛型参数的实际类型会在实际调用时才被填充(类型别名中显式传入,或者函数中隐式提取),而条件类型在这一基础上,可以基于填充后的泛型参数做进一步的类型操作:

ts
type LiteralType<T> = T extends string ? 'string' : 'other'

type Res1 = LiteralType<'给我一个div'> // "string"
type Res2 = LiteralType<999> // "other"

同三元表达式可以嵌套一样,条件类型中也常见多层嵌套:

ts
export type LiteralType<T> = T extends string
  ? 'string'
  : T extends number
    ? 'number'
    : T extends boolean
      ? 'boolean'
      : T extends null
        ? 'null'
        : T extends undefined
          ? 'undefined'
          : never

type Res1 = LiteralType<'给我一个div'> // "string"
type Res2 = LiteralType<999> // "number"
type Res3 = LiteralType<true> // "boolean"

2.分布式条件类型

提示

分布式条件类型,也称条件类型的分布式特性,是条件类型在满足一定情况下会执行的逻辑

1. 分布式作用的条件

例子:

ts
type Condition<T> = T extends 1 | 2 | 3 ? T : never

/**
 * Res1:  1 | 2 | 3
 * 理论上这里应该返回 1 | 2 | 3 | 4 | 5 但是由于代码执行时遵循了分布式条件特性,
 * 返回结果为 1 | 2 | 3
 */
type Res1 = Condition<1 | 2 | 3 | 4 | 5>

/**
 * 这里没有遵循条件时分布特性 返回 never
 */
type Res2 = 1 | 2 | 3 | 4 | 5 extends 1 | 2 | 3 ? 1 | 2 | 3 | 4 | 5 : never

条件分布式起作用的条件

  1. 类型参数需要通过泛型参数的方式传入,而不能直接进行条件类型判断(如 Res2 中)
  2. 类型参数需要是一个联合类型

2.条件分布式的效果

条件分布式特性会将联合类型拆开来,每个分支分别进行一次条件类型判断,再将最后的结果合并起来:

ts
type Demo<T> = T extends string ? '123' : '456'

type res = Demo<string | number>  // "123" | "456"
/**
 * 以上代码在执行时相当于将传入的 string | number类型拆开类执行了两次 Demo的逻辑
 *  再合并为联合类型返回:
 */
// 执行了一下逻辑:
(string extends string ? '123' : '456') | (number extends string ? '123' : '456')

内置的工具类型Exclude,Extract等就是利用了分布式条件特性实现

ts
type a = 1 | 2 | 3
type b = 2 | 3 | 5
/**
 *  type Extract<T, U> = T extends U ? T : never;
 */
type x = Extract<a, b> // 2 | 3

3.阻止分布式条件作用

在达到特定的条件下,分布式特性会起作用,但有时可能并不需要这样的特性,只是单纯的封装一个类型来判断两个联合类型的兼容性,此时可以在条件类型中的extends关键字两侧给类型添加[]来阻止分布式特性:

ts
type Demo<T, U> = T extends U ? 'true' : 'false'

// 遵循分布式特性返回 "false" | "true"
type c = Demo<1 | 2, 3 | 2 | 4>

/* extends关键字两侧的类型添加[]让其成为元组来阻止分布式特性 */
type Demo1<T, U> = [T] extends [U] ? 'true' : 'false'

// "false"
type d = Demo1<1 | 2, 3 | 2 | 4>

3.infer关键字

TypeScript 中支持通过 infer 关键字来在条件类型中提取类型的某一部分信息,简化了多条件类型的情况

需求: 定义类型别名Res<T>,当泛型T为数组时提取数组项的类型,否则直接返回传入的类型

ts
type n = number[]
type s = string[]

type Res<T> = T extends number[] ? number : T extends string[] ? string : T

type c = Res<n> // number
type d = Res<s> // string
type f = Res<boolean> // boolean

以上代码使用infer关键字简化:

ts
type n = number[]
type s = string[]
/* 使用infer推断出数组项的类型 */
type Res<T> = T extends Array<infer R> ? R : T

type c = Res<n> // number
type d = Res<s> // string
type f = Res<boolean> // boolean

提示

inferinference 的缩写,意为推断,如 infer R R 就表示 待推断的类型。 infer 只能在条件类型中使用

1. 推断函数参数类型

  1. 所有参数组成的元组类型
ts
type Func = (...args: any[]) => any
/**
 * infer R推断的时剩余参数args的类型,是一个元组
 */
type FuncParams<T extends Func> = T extends (...args: infer R) => any ? R : never

//  [val: string, v: number]
type c = FuncParams<(val: string, v: number) => void>
  1. 指定位置参数的类型
ts
type Func = (...args: any[]) => any
/**
 * 推断第二个参数的类型,被推断参数后面使用 ...any[] 表示剩余的类型集合
 */
type FuncParams<T extends Func> = T extends (...args: [any, infer R, ...any[]])
=>
any ? R : never

// number
type c = FuncParams<(val: string, v: number, c: boolean) => void>

2. 推断函数返回值类型

ts
type FuncParams<T extends Func> = T extends (...args: any[]) => infer R ? R : never

//  string | number
type c = FuncParams<(val: string) => string | number>

3. 推断数组类型

  1. 数组项类型
ts
type Item<T> = T extends Array<infer R> ? R : never
// string | number | boolean
type It = Item<[string, number, boolean]>
  1. 数组项类型调换位置
ts
type SwapStartAndEnd<T extends any[]> = T extends [
  infer Start,
  ...infer Center,
  infer End
]
  ? [End, ...Center, Start]
  : T

// ["end", number, boolean, string]
type Res = SwapStartAndEnd<[string, number, boolean, 'end']>