在 TypeScript 的世界里,如果说“接口(Interface)”定义了数据的形状,那么“泛型(Generics)”就是赋予代码灵魂的灵活性。
一、 为什么需要泛型?
想象一下,你需要写一个函数,它的功能很简单:返回传入的参数。
1 | // 初级写法:只支持 number |
问题出在哪?
使用 any 会导致类型信息的彻底丢失。如果你传入一个字符串,TS 此时只知道返回值是 any,而失去了它是 string 的上下文。
泛型(Generics)登场:
泛型就像是一个类型变量,它在定义时不确定类型,而在调用时捕获类型。
1 | function identity<T>(arg: T): T { |
二、 泛型的四大核心用法
1. 泛型函数
除了简单的返回,泛型在处理集合(如数组)时最为常用。
1 | function getFirstElement<T>(list: T[]): T | undefined { |
2. 泛型接口
在处理后端 API 返回值时,这是最经典的应用场景。
1 | interface ApiResponse<Data> { |
3. 泛型类
构建通用的数据结构,如堆栈(Stack)或队列。
1 | class GenericNumber<T> { |
4. 泛型约束 (Generic Constraints)
有时候你不想让 T 是任何类型,而是希望它具备某些属性。
1 | interface Lengthwise { |
三、 进阶
泛型之所以强大,是因为它可以配合 TS 的内置操作符进行“逻辑计算”。
1. 结合 keyof 确保对象属性安全
1 | function getProperty<T, K extends keyof T>(obj: T, key: K) { |
2. 默认泛型参数
就像 ES6 函数参数可以有默认值一样,泛型也可以。
1 | interface MyConfig<T = string> { |
3. 条件类型与 infer
这是 TS 泛型的高阶玩法,用于在类型层级进行条件判断。
1 | // 如果 T 是数组,则取其元素类型,否则直接返回 T |
四、 总结
- 命名规范:简单场景使用
T,U,V;复杂场景建议使用语义化命名(如TUser,TResponse)。 - 避免滥用:如果一个函数不涉及类型之间的关联,或者不返回基于输入类型的结构,可能不需要泛型。
- 约束先行:尽量通过
extends限制泛型的范围,而不是让它无限制地接收任何类型。 - 利用推导:TS 的类型推导非常强大,如果能自动推导出泛型,就不要显式写出
<Type>。