634 字
3 分钟
06 · 泛型基础
NOTE泛型 = “参数化的类型”。它让函数/类/接口在保持类型安全的同时适配多种类型。本篇打基础,第 7 篇讲进阶玩法。
1. 为什么需要泛型
没有泛型时,想写一个”返回传入值”的函数只能退化成 any:
function identity(arg: any): any { return arg;}const x = identity(10); // x 是 any,丢了 number 信息用泛型保留类型信息:
function identity<T>(arg: T): T { return arg;}const x = identity(10); // x: numberconst s = identity("hi"); // s: stringT 是类型参数,调用时由实参推断。返回值类型和入参类型被”绑定”在一起。
2. 泛型函数
function first<T>(arr: T[]): T | undefined { return arr[0];}first([1, 2, 3]); // number | undefinedfirst(["a", "b"]); // string | undefined多个类型参数:
function pair<K, V>(key: K, value: V): [K, V] { return [key, value];}3. 泛型约束 extends
有时你想限制 T “至少长什么样”。用 extends 约束:
interface HasLength { length: number;}function longest<T extends HasLength>(a: T, b: T): T { return a.length >= b.length ? a : b;}longest("abc", "de"); // ✅ string 有 lengthlongest([1, 2], [3]); // ✅ 数组有 length// longest(10, 20); // ❌ number 没有 length约束让 T 既保留具体类型,又能安全访问 length。
4. 泛型默认值
类型参数也能给默认:
interface ApiResponse<T = unknown> { code: number; data: T;}const r1: ApiResponse = { code: 0, data: null }; // data: unknownconst r2: ApiResponse<string> = { code: 0, data: "ok" }; // data: string默认值让可选泛型更友好,常见于库的类型定义。
5. 泛型接口
interface Result<T> { ok: boolean; value: T;}function ok<T>(value: T): Result<T> { return { ok: true, value };}6. 泛型类
class Stack<T> { private items: T[] = []; push(item: T): void { this.items.push(item); } pop(): T | undefined { return this.items.pop(); }}const s = new Stack<number>();s.push(1);实例化时指定 T,类内所有用到 T 的地方都锁定为该类型。
7. 泛型与箭头函数
给箭头函数加泛型要小心语法——<T> 易被解析为 JSX:
const identity = <T,>(arg: T): T => arg; // 加逗号 <T,> 避免歧义在 .tsx 文件里这个逗号必需。
小结
- 泛型
<T>把”类型”当参数,保留类型信息又复用逻辑。 extends约束T的形态;默认值让泛型可选。- 泛型可用于函数、接口、类,多个类型参数用
<K, V>。 .tsx中写<T,>防止被当成 JSX。
练习
- 写泛型函数
last<T>(arr: T[]): T | undefined返回最后一个元素。 - 用
T extends object约束写一个getKeys<T extends object>(o: T): (keyof T)[]。 - 写泛型类
Queue<T>实现enqueue/dequeue。 - 给
interface Box<T = string>加默认值,分别用Box和Box<number>实例化。