讲解
泛型(generics)解决「类型作为参数」的问题。设想一个返回数组首元素的函数:数组是 number[] 时返回 number,是 string[] 时返回 string——用 any 写会丢掉这层对应关系。泛型的写法 function first(arr: T[]): T 引入了类型参数 T:调用时编译器根据实参把 T 推断为具体类型,返回值随之精确。类型参数就是一个「在调用时才确定的类型变量」,命名惯例是 T、U、K、V 或有语义的名字(TItem)。
泛型可以用于函数、接口、类、类型别名。interface Box { value: T } 定义了一个「装任意东西的盒子」类型;class Stack 则让栈这种经典数据结构对任意元素类型保持类型安全。使用时可以显式传类型参数(first([1, 2])),更多时候让编译器从实参推断。
调用时的推断规则值得理解:编译器收集所有出现 T 的参数位置,反推 T 的值;推不出来时报错,这时要么给默认值(<T = string>),要么显式指定。多个类型参数各自独立推断,function merge<A, B>(a: A, b: B): [A, B] 一次调用就能固定两个类型。
示例
泛型函数:类型随实参走:
function first<T>(arr: T[]): T | undefined {
return arr.length === 0 ? undefined : arr[0];
}
const n: number | undefined = first([10, 20, 30]);
const s: string | undefined = first(["a", "b"]);
console.log(n, s);
泛型接口与类型别名:
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
type Book = { title: string; price: number };
const resp: ApiResponse<Book[]> = {
code: 0,
message: "ok",
data: [{ title: "活着", price: 39 }],
};
console.log(`${resp.message}:${resp.data[0].title}`);
泛型类:
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
get size(): number {
return this.items.length;
}
}
const stack = new Stack<string>();
stack.push("第一层");
stack.push("第二层");
console.log(`弹出 ${stack.pop()},剩余 ${stack.size}`);
常见坑
- 用 any 代替泛型:function identity(x: any): any 编译能过,但输入输出的类型联系断了——泛型存在的意义就是保住这层联系。
- 类型参数只出现一次:function f(x: T): void 里的 T 没有任何约束价值,通常说明该位置直接写具体类型或 unknown 更合适。
- 显式传参与推断打架:first([1]) 必然报错;显式传参前先想清楚实参是否匹配,多数情况让编译器推断。
- 在泛型函数里假设 T 的能力:T 是「任意类型」,不能对 T 的值调 toFixed——那需要约束,下一章就讲。
小结
泛型 = 类型的参数化,T 在调用时确定;可用于函数、接口、类、别名;优先让编译器推断。下一章讲泛型约束、默认类型参数,正式进入进阶。