1. 核心痛点:丢失的“身份证”
在没有泛型之前,我们想写一个通用的函数,通常会遇到两个极端:
- 写死类型: 只能传
number,传string就报错。太死板。 - 使用
any: 什么都能传,但进去之后 “身份证”丢了。
举个“丢失身份证”的例子:
// 使用 any,就像一个黑盒
function heuristic(val: any): any {
return val;
}
const result = heuristic("hello");
// 🚨 问题来了:TypeScript 现在只知道 result 是 any 类型。
// 你输入 result. 时,代码提示全没了!
// 甚至写 result.toFixed()(数字的方法)它都不报错,直到运行时崩溃。
泛型的真正作用: 它是用来保住类型“身份证”的。
2. 基础思维模型:透明管道(针对函数)
把泛型函数想象成一个透明的管道,而 <T> 就是管道入口的一个扫描仪。
// 这里的 <T> 是一份“契约”
// 它在说:嗨,进来的是什么类型 (T),出去的必须也是同一种类型 (T)!
function tunnel<T>(val: T): T {
return val;
}
请跟着这个过程走一遍:
- 当你调用
tunnel("hello")时。 - 扫描仪
<T>瞬间启动,它捕捉到了"hello"的类型是 String。 - 于是,在这个瞬间,函数内部所有的
T自动变成了String。 - 最关键的一步: 它不仅处理了数据,还把 String 这个类型贴在了返回值上。
对比一下:
- any (黑盒): 苹果进去 -> 黑盒处理 -> 吐出一个“东西”(可能是苹果,也可能是炸弹)。
- 泛型 (透明管道): 苹果进去 -> T 记录下“这是苹果” -> 管道处理 -> 吐出一个带着“苹果”标签的苹果。
3. 进阶思维模型:万能 USB 接口(针对接口与约束)
函数像管道,而 接口(Interface) 更像我们熟悉的 USB 标准。
A. 痛苦的过去:定制插座(没有泛型)
十几年前,诺基亚、iPhone、相机都有各自奇形怪状的充电口。
痛点: 只要来个新设备,你就得重新造一条线(写重复的代码)。
B. 现代的解法:USB-C 接口(泛型)
现在,一条 USB-C 线走天下。它只定义了一套传输标准,不关心你插的是鼠标还是键盘。
// ✅ 泛型接口:USB <T>
// 这是一条万能线,T 代表“连接的设备类型”
interface USB<T> {
connect(device: T): void;
data: T;
}
// 场景 1:插鼠标 -> 线变成鼠标专用
const mousePort: USB<Mouse> = ...;
// 场景 2:插键盘 -> 线变成键盘专用
const keyboardPort: USB<Keyboard> = ...;
C. 泛型约束(extends):防呆设计
USB 接口虽然万能,但也不能瞎插。你不能把水管插到 USB 口里吧?
这就对应 TypeScript 的 泛型约束 (extends)。
// 规定:T (插入的设备) 必须是 "电子设备" (Electronics)
// extends 就像物理防呆设计:不符合形状的根本插不进去
function connectUSB<T extends Electronics>(device: T) {
device.powerOn(); // 因为必须是电子设备,所以一定能开机
}
// ❌ 报错:水不是电子设备
connectUSB(new Water());
4. 总结
现在请这样记住它:
泛型就是一种“类型传声筒”或者“类型关联器”。
它的核心价值只有两个:
- 灵活: 像
any一样,允许你传入不同类型(万能接口)。 - 严谨: 像具体类型一样,记住你传了什么,并在后续操作中锁死这个关系(防呆设计)。