infer 模式匹配
infer 是类型层的解构赋值:从一个结构里"抠出"你想要的那部分。
基本形态
ts
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never
type A = ReturnTypeOf<() => string>
type B = ReturnTypeOf<(x: number) => boolean>
infer R 的意思是:"这里有个位置我不知道是什么类型,让编译器推断出来,存到 R 里。"
infer 只能出现在条件类型的 extends 子句里。
对照 JS 的解构
| JS | 类型层 |
|---|---|
const { a } = obj | T extends { a: infer A } ? A : never |
const [first] = arr | T extends [infer F, ...unknown[]] ? F : never |
const { a: { b } } = obj | T extends { a: { b: infer B } } ? B : never |
常见模式
函数
ts
type ParamsOf<T> = T extends (...args: infer P) => any ? P : never
type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never
type A = ParamsOf<(x: string, y: number) => void>
type B = ReturnOf<(x: string) => number>
数组与元组
ts
type First<T extends unknown[]> = T extends [infer F, ...unknown[]] ? F : never
type Last<T extends unknown[]> = T extends [...unknown[], infer L] ? L : never
type Tail<T extends unknown[]> = T extends [unknown, ...infer R] ? R : never
type A = First<[1, 2, 3]>
type B = Last<[1, 2, 3]>
type C = Tail<[1, 2, 3]>
Promise
ts
type Unwrap<T> = T extends Promise<infer U> ? U : T
type A = Unwrap<Promise<string>>
type B = Unwrap<Promise<Promise<number>>>
注意嵌套 Promise 只解一层,要递归解用官方的 Awaited。
字符串
ts
type SplitDash<S extends string> = S extends `${infer A}-${infer B}` ? [A, B] : [S]
type A = SplitDash<'a-b'>
type B = SplitDash<'abc'>
模板字面量里的 ${infer X} 是非贪婪的:只匹配到第一个分隔符。
对象
ts
type ValueOfKey<T, K extends keyof T> = T extends { [P in K]: infer V } ? V : never
interface Todo {
title: string
done: boolean
}
type A = ValueOfKey<Todo, 'title'>
多个 infer
一次可以声明多个:
ts
type Swap<T> = T extends [infer A, infer B] ? [B, A] : T
type A = Swap<[string, number]>
infer 带约束(TS 4.8)
ts
type GetLength<T> = T extends { length: infer L extends number } ? L : never
type A = GetLength<string[]>
type B = GetLength<[1, 2, 3]>
好处是推断失败时不用写 never 兜底逻辑,编译器会直接认为不匹配。
递归 infer
ts
type DeepUnwrap<T> = T extends Promise<infer U> ? DeepUnwrap<U> : T
type A = DeepUnwrap<Promise<Promise<Promise<boolean>>>>
这就是 Awaited 的简化版(官方版还处理了 thenable 对象)。
联合分发下的 infer
ts
type ElementOf<T> = T extends (infer U)[] ? U : never
type A = ElementOf<string[] | number[]>
分发让每个成员各自推断,结果是联合。
坑
1. infer 在协变/逆变位置的结果不同
ts
type InReturn<T> = T extends () => infer R ? R : never
type InParam<T> = T extends (x: infer P) => void ? P : never
type A = InReturn<() => 'a'>
type B = InParam<(x: 'a' | 'b') => void>
// 多个参数时推断成什么?
type InParams<T> = T extends (...args: infer P) => any ? P : never
type C = InParams<(x: string, y: number) => void>
参数位置是逆变的,多个参数会推断成元组;如果同一位置有多个候选(函数重载),会取交叉。
2. infer 推断不出来就是 never… 或不匹配
ts
type F<T> = T extends [infer A] ? A : 'not a tuple'
type A = F<[string]>
type B = F<string>
推断失败 = 条件不成立 = 走 false 分支,不是 never。
3. 分发会干扰 infer
ts
type F<T> = T extends [infer A] ? A : never
type G = F<[string] | [number]>
想要整体判断时用 [T] extends [[infer A]] 这类包一层写法。
实战:从异步流反推元素类型
ts
declare function streamChat(): AsyncGenerator<{ delta: string }, void, unknown>
type ChunkOf<T> = T extends AsyncGenerator<infer U, any, any> ? U : never
type Chunk = ChunkOf<ReturnType<typeof streamChat>>
type ElemOf<T> = T extends AsyncIterable<infer U> ? U : never这是做流式 AI 响应时最常用的一个技巧,详见异步与迭代器。