1. 核心痛点:丢失的"身份证"
在没有泛型之前,我们想写一个通用的函数(比如返回你传给它的值),通常会遇到两个极端:
- 写死类型: 只能传
number,传string就报错。太死板。 - 使用
any: 什么都能传,但进去之后 "身份证"丢了。
举个"丢失身份证"的例子:
TypeScript
// 使用 any,就像一个黑盒
function heuristic(val: any): any {
return val;
}
const result = heuristic("hello");
// 🚨 问题来了:TypeScript 现在只知道 result 是 any 类型。
// 你打 result. specifically... 这里的代码提示全没了!
// 甚至写 result.toFixed() (这是数字的方法)它都不报错,直到运行时崩溃。
泛型的真正作用: 它是用来保住类型"身份证"的。
2. 思维模型:透明管道(The Transparent Tunnel)
把泛型函数想象成一个透明的管道 ,而 <T> 就是管道入口的一个扫描仪。
TypeScript
function tunnel<T>(val: T): T {
return val;
}
请跟着这个过程走一遍:
- 当你调用
tunnel("hello")时。 - 扫描仪
<T>瞬间启动,它捕捉到了"hello"的类型是 String。 - 于是,在这个瞬间,函数内部所有的
T自动变成了String。 - 最关键的一步: 它不仅处理了数据,还把 String 这个类型贴在了返回值上。
对比一下:
- any (黑盒): 苹果进去 -> 黑盒处理 -> 吐出一个"东西"(可能是苹果,也可能是炸弹)。
- 泛型 (透明管道): 苹果进去 -> T 记录下"这是苹果" -> 管道处理 -> 吐出一个带着"苹果"标签的苹果。
3. 现实生活类比:万能USB接口 vs 定制插座
为了彻底弄懂,我们用接口做比喻。
场景:你需要生产一种"万能包装盒"
方案 A(不用泛型 - 类似于 any):
你造了一个巨大的袋子。
- 用户放入一支口红。
- 拿出来时,系统只标注这是"一个物品"。
- 用户甚至可能试图把这个"物品"当成汉堡吃掉,因为系统没告诉他这是口红。
方案 B(使用泛型):
你造了一个智能模具。
- 当用户把"口红"放进去的那一刻,模具自动变形卡住口红,并打上标签: "内含:口红" 。
- 当用户把"汉堡"放进去,模具自动变形卡住汉堡,并打上标签: "内含:汉堡" 。
代码映射:
TypeScript
// 这是一个智能盒子 (Box)
// <T> 是这一轮生产的"物品类型"
interface Box<T> {
content: T; // 内容物就是 T 类型
}
// 场景 1:装口红
const lipstickBox: Box<Lipstick> = { content: myLipstick };
// ✅ TypeScript 知道:lipstickBox.content 是口红,不能吃。
// 场景 2:装汉堡
const burgerBox: Box<Burger> = { content: myBurger };
// ✅ TypeScript 知道:burgerBox.content 是汉堡,可以吃。
4. 再次审视那个"难懂"的语法
现在回头看最基础的语法,是不是清晰了?
TypeScript
// 这里的 <T> 是一份"契约"
// 它在说:嗨,进来的是什么类型 (参数 val: T),出去的必须也是同一种类型 (返回值: T)!
function identify<T>(val: T): T {
return val;
}
- T 不是值 ,它是一个占位符。
- 它的作用是捕获 你调用函数那一刻传入的具体类型,然后把这个类型传递给参数和返回值。
5. 进阶:为什么要用 extends (泛型约束)?
有时候管道太宽了,我们想限制一下。
比喻: 这是一个微波炉泛型。
- 你可以放汉堡、放牛奶(T 可以变)。
- 但你绝对不能放金属。
所以我们需要给 T 加个过滤器:
TypeScript
// 这里的 extends 表示"必须包含"
// T 必须包含 .length 属性,否则微波炉不工作
function logLength<T extends { length: number }>(arg: T) {
console.log(arg.length);
}
logLength("abc"); // ✅ 字符串有 length
logLength([1,2]); // ✅ 数组有 length
logLength(100); // ❌ 报错!数字没有 length,被拒之门外
总结
现在请这样记住它:
泛型就是一种"类型传声筒"或者"类型关联器"。
它的核心目的只有两个:
- 灵活:
any,允许你传入不同类型。 - 严谨: 具体类型,记住 你传了什么,并在后续操作中锁死这个关系,不让类型信息丢失。