条件类型

条件类型根据条件选择类型,类似于三元表达式。条件类型让类型系统更加灵活,可以根据输入类型动态确定输出类型。

基本语法

条件类型使用 extends 关键字和三元表达式语法。

type IsString<T> = T extends string ? true : false

type A = IsString<string>
type B = IsString<number>

const a: A = true
const b: B = false

T extends string ? true : false 表示如果 Tstring 的子类型,则类型是 true,否则是 false

条件类型与联合类型

条件类型会分布式应用于联合类型。

type ToArray<T> = T extends any ? T[] : never

type StringOrNumberArray = ToArray<string | number>

const arr: StringOrNumberArray = ["hello"]

ToArray<string | number> 分布式应用,结果是 string[] | number[]

禁止分布式行为

使用元组可以禁止分布式行为。

type ToArrayNonDist<T> = [T] extends [any] ? T[] : never

type Result = ToArrayNonDist<string | number>

const arr: Result = ["hello", 1]

[T] extends [any] 避免了分布式应用,结果是 (string | number)[]

infer 关键字

infer 关键字可以在条件类型中推断类型。

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

function greet(name: string): string {
  return `你好,${name}`
}

type GreetReturn = ReturnType<typeof greet>

const result: GreetReturn = "你好,张三"

infer R 推断函数的返回值类型,R 就是返回值类型。

提取数组元素类型

使用 infer 提取数组的元素类型。

type ElementType<T> = T extends (infer E)[] ? E : never

type StringElement = ElementType<string[]>
type NumberElement = ElementType<number[]>

const str: StringElement = "hello"
const num: NumberElement = 123

infer E 推断数组的元素类型。

提取 Promise 值类型

使用 infer 提取 Promise 的值类型。

type Awaited<T> = T extends Promise<infer U> ? U : T

type StringPromise = Awaited<Promise<string>>
type NumberValue = Awaited<number>

const str: StringPromise = "hello"
const num: NumberValue = 123

infer U 推断 Promise 的值类型。

提取函数参数类型

使用 infer 提取函数的参数类型。

type Parameters<T> = T extends (...args: infer P) => any ? P : never

function greet(name: string, age: number): void {}

type GreetParams = Parameters<typeof greet>

const params: GreetParams = ["张三", 25]

infer P 推断函数的参数类型元组。

条件类型约束

条件类型可以约束类型参数。

type Message<T extends { message: string }> = T["message"]

interface Success {
  message: string
  code: number
}

type SuccessMessage = Message<Success>

const msg: SuccessMessage = "操作成功"

T extends { message: string } 约束 T 必须有 message 属性。

嵌套条件类型

条件类型可以嵌套使用。

type TypeName<T> =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object"

type T1 = TypeName<string>
type T2 = TypeName<number[]>
type T3 = TypeName<() => void>

const t1: T1 = "string"
const t2: T2 = "object"
const t3: T3 = "function"

嵌套条件类型可以处理多种情况。

实际应用

条件类型在实际开发中常用于创建类型守卫函数。

type NonNullable<T> = T extends null | undefined ? never : T

function assertNotNull<T>(value: T): NonNullable<T> {
  if (value === null || value === undefined) {
    throw new Error("值不能为空")
  }
  return value as NonNullable<T>
}

const value: string | null = "hello"
const nonNull = assertNotNull(value)

console.log(nonNull.toUpperCase())

NonNullable<T> 排除了 nullundefined 类型。

小结

条件类型根据条件选择类型,使用 extends 关键字和三元表达式语法。条件类型会分布式应用于联合类型。infer 关键字可以在条件类型中推断类型,常用于提取函数返回值、数组元素、Promise 值和函数参数类型。条件类型可以嵌套使用,处理多种情况。条件类型是创建灵活类型的重要工具。