5 个复杂泛型的拆解思路,教你从看不懂到写得出!

5 个复杂泛型的拆解思路,教你从看不懂到写得出!

📌 点赞收藏关注不迷路!
你是否在 TypeScript 的源码、第三方库里看过这样的泛型:

type DeepPartial<T> = {
  [P in keyof T]?: DeepPartial<T[P]>
}

或者这样的:

type Flatten<T> = T extends (infer U)[] ? U : T

是不是一看就觉得“这都是什么天书”?别怕!

这篇文章带你掌握 5 个常见复杂泛型的拆解思路与实战案例从看不懂到能写出,一套搞定!


🧠 总体思路:拆解复杂泛型的 5 步

无论泛型多复杂,我们都可以按这 5 步来拆解它:

步骤说明
1️⃣ 泛型参数:泛型变量 T 是谁?
2️⃣ 条件判断:有没有 extends 条件判断?
3️⃣ 推断结构:是否用 infer 推导?
4️⃣ 映射类型:是否用了 keyof / in 遍历?
5️⃣ 递归调用:是否对自己递归调用?

🎯 案例 1:DeepPartial — 递归让对象变“半透明”

type DeepPartial<T> = {
  [P in keyof T]?: DeepPartial<T[P]>
}

💡 这是做什么的?

将对象中所有属性都变为可选,并且嵌套对象也一样可选


🧩 拆解思路:

步骤解释
1️⃣ 泛型参数 T任意类型,如对象 { a: string; b: { c: number } }
4️⃣ 映射类型 [P in keyof T]遍历 T 的所有属性
5️⃣ 递归调用 DeepPartial<T[P]>对属性值再次应用该类型
➕ 可选符号 ?将每个属性设为可选

✅ 使用效果:

type User = {
  name: string
  profile: {
    age: number
    city: string
  }
}

type OptionalUser = DeepPartial<User>

// 相当于:
/*
{
  name?: string
  profile?: {
    age?: number
    city?: string
  }
}
*/

🎯 案例 2:Flatten — 从数组中提取基础类型

type Flatten<T> = T extends (infer U)[] ? U : T

💡 这是做什么的?

如果传入的是数组类型,比如 string[],就返回 string;否则原样返回。


🧩 拆解思路:

步骤解释
1️⃣ 泛型 T任意类型,比如 string[]number
2️⃣ 条件判断 extends判断是否为数组类型
3️⃣ 推导 infer U推导出数组中元素类型
结果如果是数组就提取,否则原样返回

✅ 使用效果:

type A = Flatten<number[]> // number
type B = Flatten<string>   // string

🎯 案例 3:PickByType — 挑出类型为某种类型的属性

type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K]
}

💡 这是做什么的?

从对象中筛选出类型为 U 的字段


🧩 拆解思路:

步骤解释
1️⃣ 泛型参数 T/UT 是原对象类型,U 是目标类型
4️⃣ 映射类型 [K in keyof T as ...]遍历属性并用 as 改名或剔除
2️⃣ 条件判断 T[K] extends U如果该属性是目标类型就保留
else → never被映射为 never 的键会被丢弃

✅ 使用效果:

type Example = {
  id: number
  name: string
  age: number
}

type OnlyNumbers = PickByType<Example, number>
// => { id: number; age: number }

🎯 案例 4:UnionToIntersection — 联合转交叉类型

type UnionToIntersection<U> =
  (U extends any ? (arg: U) => any : never) extends (arg: infer R) => any
    ? R
    : never

💡 这是做什么的?

将联合类型变成交叉类型:

type A = { a: string }
type B = { b: number }
type U = A | B
type I = UnionToIntersection<U> // { a: string } & { b: number }

🧩 拆解思路:

步骤解释
1️⃣ 泛型参数 U是联合类型 A | B
2️⃣ 条件判断 U extends any利用分布式条件类型,逐一取出 A/B
3️⃣ infer R 提取参数类型推导出合并后的参数类型
Trick利用函数参数的交叉特性,得到交叉类型

📌 这是一个 高级技巧,核心是:

TS 函数参数是逆变的,所以可以用来“合并”联合类型!


🎯 案例 5:IsAny — 判断某个类型是否是 any

type IsAny<T> = 0 extends (1 & T) ? true : false

💡 这是做什么的?

这是判断一个类型是否为 any 的方法,TS 原生不提供,需用技巧实现。


🧩 拆解思路:

步骤解释
利用交叉类型:1 & T如果 T 是 any,则 1 & anyany
判断 0 extends any永远为 true,因此结果是 true
其他类型不会满足条件所以返回 false

✅ 使用效果:

type A = IsAny<any>       // true
type B = IsAny<string>    // false
type C = IsAny<unknown>   // false

🧠 总结:复杂泛型,其实也讲“套路”

泛型写法技术点关键词
extends类型判断条件判断
infer推导类型变量函数参数、数组、返回值
keyof / in遍历属性映射类型
递归引用处理嵌套结构Tree / JSON
as + never过滤属性名Pick/Omit 高阶版本

掌握这些套路,你不仅能看懂源码中奇怪的泛型,甚至能自己写出来!


✅ 最后说一句

如果你看完这篇文章感觉自己对复杂泛型没那么害怕了,请给我点个 点赞 + 收藏 + 关注 吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值