Last updated on Oct 6, 2022

条件类型

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

const value = valueA === valueB ? result1 : result2

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

type LiteralType<T> = T extends string ? 'string' : 'number'

type Res1 = LiteralType<'sam'> // "string"
type Res2 = LiteralType<599> // "number"

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

type LiteralToPrimitive<T> = T extends number
  ? number
  : T extends bigint
  ? bigint
  : T extends string
  ? string
  : never

条件类型也可以用来对更复杂的类型进行比较。

type Func = (...args: any[]) => any

type FunctionConditionType<T extends Func> = T extends (
  ...args: any[]
) => string
  ? 'A string return func!'
  : 'A non-string return func!'

//  "A string return func!"
type StringResult = FunctionConditionType<() => string>
// 'A non-string return func!';
type NonStringResult1 = FunctionConditionType<() => boolean>
// 'A non-string return func!';
type NonStringResult2 = FunctionConditionType<() => number>

这里同时存在泛型约束条件类型,但它们产生作用的时机完全不同。

infer 关键字

TypeScript 中支持通过 infer 关键字来在条件类型中提取类型的某一部分信息,比如上面我们要提取函数返回值类型:

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

infer是 inference 的缩写(推断),如 infer R 中 R 就表示 待推断的类型infer 只能在条件类型中使用。

类型结构除了函数类型结构之外,还可以是数组、接口等

// 数组
type Swap<T extends any[]> = T extends [infer A, infer B] ? [B, A] : T

type SwapResult1 = Swap<[1, 2]> // 符合元组结构,首尾元素替换[2, 1]
type SwapResult2 = Swap<[1, 2, 3]> // 不符合结构,没有发生替换,仍是 [1, 2, 3]

// 接口
type PropType<T, K extends keyof T> = T extends { [Key in K]: infer R }
  ? R
  : never

type PropTypeResult1 = PropType<{ name: string }, 'name'> // string
type PropTypeResult2 = PropType<{ name: string; age: number }, 'name' | 'age'> // string | number

// 反转键名与键值
type ReverseKeyValue<T extends Record<string, unknown>> = T extends Record<
  infer K,
  infer V
>
  ? Record<V & string, K> // & string 可以确保属性名为 string 类型
  : never

type ReverseKeyValueResult1 = ReverseKeyValue<{ key: 'value' }> // { "value": "key" }