为什么需要进阶泛型
基础泛型解决”类型参数化”(Array<T>),进阶泛型解决”根据输入类型推导输出类型“。比如:
- 根据函数拿到返回类型(
ReturnType)
- 根据对象类型挑出部分属性(
Pick)
- 根据数组类型取出元素类型(
ElementType)
这些能力都建立在两个核心机制上:条件类型和 infer。
一、条件类型:类型层面的 if/else
1 2 3 4
| type IsString<T> = T extends string ? true : false
type A = IsString<'hello'> type B = IsString<123>
|
注意:T extends string 在类型层面不是”继承”,而是”T 是否可以赋值给 string“。
分布式条件类型
当 T 是裸类型参数(没有用 [] 包裹)时,条件类型会”分发”到联合类型的每个成员:
1 2 3 4 5 6
| type ToArray<T> = T extends any ? T[] : never
type R = ToArray<string | number>
|
这个”分发”行为是很多工具类型(如 Exclude)的实现基础:
1 2 3 4 5
| type MyExclude<T, U> = T extends U ? never : T
type R = MyExclude<'a' | 'b' | 'c', 'a'>
|
二、infer:从类型里”抽出”一部分
infer 只能在条件类型的 extends 分支里使用,作用是从一个类型结构里推断出某个位置的类型:
1 2 3 4 5 6 7 8 9 10
| type ElementType<T> = T extends (infer U)[] ? U : never
type A = ElementType<string[]> type B = ElementType<(number)[]>
type Awaited<T> = T extends Promise<infer U> ? U : T
type R = Awaited<Promise<string>>
|
更实战的例子:函数返回类型:
1 2 3 4 5 6 7 8
| type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never
function getUser() { return { id: 1, name: '张三' } }
type User = MyReturnType<typeof getUser>
|
三、手写常用工具类型
理解了条件类型和 infer,内置工具类型都能自己写出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| type MyPartial<T> = { [K in keyof T]?: T[K] }
type MyPick<T, K extends keyof T> = { [P in K]: T[P] }
type MyRecord<K extends keyof any, T> = { [P in K]: T }
type MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
type Flatten<T> = T extends (infer U)[] ? U extends any[] ? Flatten<U> : U : T
type R = Flatten<number[][][]>
|
四、条件类型递归:类型层面的循环
条件类型天然支持递归,这是类型体操的核心能力。链式取值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| type DeepValue<T, Path extends string> = Path extends `${infer K}.${infer Rest}` ? K extends keyof T ? DeepValue<T[K], Rest> : never : Path extends keyof T ? T[Path] : never
type User = { profile: { address: { city: string } } }
type City = DeepValue<User, 'profile.address.city'>
|
模板字符串类型(${infer K}.${infer Rest})让类型也能解析字符串,是路由类型、路径类型的基础。
五、实战:给接口请求写类型安全封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| interface ApiMap { '/user/info': { id: number; name: string } '/user/orders': { list: Order[] } }
type ApiResponse<K extends keyof ApiMap> = Promise<ApiMap[K]>
async function request<K extends keyof ApiMap>(url: K): ApiResponse<K> { const res = await fetch(url) return res.json() }
const user = await request('/user/info')
|
六、常见坑
any 会破坏条件类型:T extends string 当 T 是 any 时会同时走两个分支,用 unknown 代替;
- infer 位置错误:infer 只能出现在
extends 的右侧结构中;
- 递归过深报错:TS 对类型递归有深度限制(约 1000 层),复杂递归要注意拆解;
- 分布式条件类型踩坑:不确定时用
[T] extends [U] 关掉分发。
总结
进阶泛型的三个台阶:条件类型(分支)→ infer(提取)→ 递归(循环)。日常工作中不追求炫技,但看懂并封装 ReturnType、Pick 这类工具,以及用类型映射约束接口请求,能显著减少 any 和低级错误。