TypeScript 泛型与类型体操入门
更新时间:2026-08-29。本文回答:泛型
<T>怎么用?怎么给类型参数加约束?keyof/映射类型/条件类型到底在做什么?
一、泛型:类型的参数
泛型让函数/接口/类"延迟指定类型",同时保持类型安全:
ts
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const a = first([1, 2, 3]); // a: number
const b = first(["x", "y"]); // b: string二、给类型参数加约束
用 extends 限制 T 必须拥有某些形状:
ts
function max<T extends { value: number }>(a: T, b: T): T {
return a.value > b.value ? a : b;
}T extends U读作"T 必须是 U 的子类型",否则调用报错。- 约束让泛型既能复用,又能安全访问成员。
三、keyof 与索引类型
ts
type User = { id: number; name: string };
type Keys = keyof User; // "id" | "name"
type Values = User[keyof User]; // number | string
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]; // 返回值类型精确跟随 key
}keyof T取出对象的所有键(联合类型)。T[K]是"索引访问类型",让返回类型随传入的 key 动态变化——这是类型安全的动态访问。
四、映射类型与条件类型
映射类型:基于旧类型批量生成新类型。
ts
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T> = { [K in keyof T]?: T[K] };条件类型:类型层面的三元表达式,配合 infer 提取。
ts
type ElementType<T> = T extends (infer E)[] ? E : T;
type A = ElementType<number[]>; // numberTS 内置的 Partial/Pick/Omit/Record 等都是用这两套机制实现的,统称"工具类型"。
五、与其他语言泛型对比
| 维度 | TypeScript | Rust | C++ |
|---|---|---|---|
| 实例化 | 类型擦除(无运行时代码) | 单态化(生成多份) | 模板实例化 |
| 约束 | extends | trait bound | concept/where |
| 元编程 | 条件/映射类型 | 宏/ trait | 模板特化/SFINAE |
| 运行时痕迹 | 无 | 无 | 无 |
六、与本站主线的衔接
| TS 机制 | 本站对应 | 衔接文档 |
|---|---|---|
| 泛型复用 | C++ 模板 | 抽象与单态化跨语言对照 |
| 约束/边界 | Rust trait bound | 类型约束同源 |
| 类型即契约 | 接口设计原则 | SOLID 中的接口隔离 |
| 构建期检查 | 前端构建工具 | tsc 在构建链里跑 |
七、常见坑
- 过度类型体操:深层条件类型可读性差,团队应约定"工具类型适度即可"。
anyvs 泛型:能用泛型参数化的别用any,否则失去类型追踪。- 运行时无类型:
instanceof检查的是运行时对象,类型层面的extends不影响运行时行为。
一句话总结
TS 泛型 = <T> 延迟类型 + extends 约束 + keyof/映射/条件类型做类型级变换——把"形状的变换规则"在编译期算出来,工程重构时类型自动跟着走。
继续:工程配置与构建。