泛型:TypeScript 的核心思维模型

64 阅读3分钟

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;
}

请跟着这个过程走一遍:

  1. 当你调用 tunnel("hello") 时。
  2. 扫描仪 <T> 瞬间启动,它捕捉到了 "hello" 的类型是 String
  3. 于是,在这个瞬间,函数内部所有的 T 自动变成了 String
  4. 最关键的一步: 它不仅处理了数据,还把 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. 总结

现在请这样记住它:

泛型就是一种“类型传声筒”或者“类型关联器”。

它的核心价值只有两个:

  1. 灵活:any 一样,允许你传入不同类型(万能接口)。
  2. 严谨: 像具体类型一样,记住你传了什么,并在后续操作中锁死这个关系(防呆设计)。