一、前言
在 TypeScript 开发中,我们经常会遇到需要编写可复用、灵活且类型安全的函数或组件的情况。传统的做法是使用 any 类型来处理未知类型,但这会牺牲类型检查的优势。
TypeScript 的泛型(Generics) 正是为了解决这个问题而设计的——它允许我们在定义函数、接口或类时,不预先指定具体类型,而在使用时再指定类型,从而实现类型参数化和类型安全的复用。
本文将带你全面了解 TypeScript 中的泛型机制,包括:
✅ 泛型函数的定义与调用
✅ 泛型接口与类的使用
✅ 泛型约束与默认类型
✅ 常见泛型工具类型(Partial、Required、Record 等)
✅ 实际开发中的典型应用案例
并通过大量代码示例帮助你深入理解泛型的精髓。
二、什么是泛型?
✅ 定义:
泛型是一种编程范式,它允许我们在编写函数、接口或类时使用类型变量(type variable),这些变量会在实际调用时被具体的类型替换。
⚠️ 核心优势:
- 提高代码复用性
- 保持类型安全性
- 避免使用
any导致的类型丢失 - 支持多种数据类型的统一处理
三、泛型函数(Generic Functions)
✅ 示例1:基本泛型函数
function identity<T>(arg: T): T {
return arg;
}
const output1 = identity<string>("Hello"); // string 类型
const output2 = identity<number>(42); // number 类型
✅ 示例2:类型推断调用泛型函数
const output3 = identity(true); // 自动推断为 boolean 类型
四、泛型接口(Generic Interfaces)
✅ 示例:定义一个泛型接口
interface Box<T> {
value: T;
}
const box1: Box<string> = { value: "Apple" };
const box2: Box<number> = { value: 123 };
五、泛型类(Generic Classes)
✅ 示例:泛型类的定义与使用
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const numberStack = new Stack<number>();
numberStack.push(10);
console.log(numberStack.pop()); // 10
const stringStack = new Stack<string>();
stringStack.push("Hello");
console.log(stringStack.pop()); // Hello
六、泛型约束(Generic Constraints)
有时我们需要限制泛型的类型范围,例如只能是某个接口的子类型。
✅ 示例:使用 extends 添加泛型约束
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(arg.length);
}
logLength("Hello"); // 字符串有 length 属性 ✅
logLength([1, 2, 3]); // 数组也有 length 属性 ✅
// logLength(42); // ❌ 报错:number 没有 length 属性
七、多个类型参数
你可以定义多个泛型参数,用于更复杂的逻辑处理。
✅ 示例:多泛型参数函数
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
const result = pair<string, number>("age", 25); // ["age", 25]
八、泛型工具类型(Utility Types)
TypeScript 内置了许多常用的泛型工具类型,极大提升了开发效率。
✅ 1. Partial<T> —— 将所有属性变为可选
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = Partial<User>;
// 相当于:
// {
// id?: number;
// name?: string;
// email?: string;
// }
const user: PartialUser = { name: "Alice" }; // 合法
✅ 2. Required<T> —— 所有属性必须存在
type RequiredUser = Required<PartialUser>;
// 所有字段都变回必填
✅ 3. Readonly<T> —— 所有属性只读
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { id: 1, name: "Alice", email: "a@example.com" };
// user.id = 2; ❌ 错误:属性是只读的
✅ 4. Record<K, T> —— 创建键值对对象
type StatusMap = Record<string, number>;
const status: StatusMap = {
loading: 0,
success: 1,
error: 2
};
✅ 5. Pick<T, K> —— 从类型 T 中选取部分属性
type UserNameAndEmail = Pick<User, 'name' | 'email'>;
const info: UserNameAndEmail = {
name: "Bob",
email: "bob@example.com"
};
✅ 6. Omit<T, K> —— 排除某些属性
type UserWithoutId = Omit<User, 'id'>;
const user: UserWithoutId = {
name: "Charlie",
email: "charlie@example.com"
};
九、泛型默认类型(Default Generic Types)
你可以为泛型提供默认类型,提高灵活性。
✅ 示例:设置泛型默认值
interface Response<T = string> {
data: T;
status: number;
}
const res1: Response = { data: "OK", status: 200 }; // 默认 string
const res2: Response<number> = { data: 123, status: 200 };
十、实际开发中的泛型应用场景
✅ 场景1:通用 API 请求封装
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
return await response.json();
}
interface User {
id: number;
name: string;
}
const user = await fetchData<User>("/api/user/1");
console.log(user.name);
✅ 场景2:React 组件 props 类型抽象
interface ListProps<T> {
items: T[];
renderItem: (item: T) => JSX.Element;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}
// 使用:
<List<string>
items={["Apple", "Banana"]}
renderItem={(item) => <span>{item}</span>}
/>
十一、注意事项与最佳实践
| 场景 | 建议 |
|---|---|
是否推荐使用泛型代替 any | ✅ 是,避免类型丢失 |
| 是否建议泛型命名清晰 | ✅ 是,如 T, U, K, V 等标准命名 |
| 是否应避免过度泛型化 | ✅ 是,仅在需要复用时使用 |
| 是否推荐结合接口约束使用 | ✅ 是,增强类型安全性 |
| 是否支持泛型重载? | ✅ 是,但需谨慎使用 |
十二、总结对比表:TypeScript 泛型常用特性一览
| 特性 | 示例 | 说明 |
|---|---|---|
| 泛型函数 | function identity<T>(arg: T) | 类型由调用决定 |
| 泛型接口 | interface Box<T> | 描述泛型结构 |
| 泛型类 | class Stack<T> | 可复用的数据结构 |
| 泛型约束 | T extends Lengthwise | 限制类型范围 |
| 多个泛型参数 | <K, V> | 更复杂逻辑支持 |
| 默认泛型类型 | T = string | 增强灵活性 |
| 工具类型 | Partial<T>, Readonly<T> | 快速构建新类型 |
| 实际用途 | 数据结构、API、组件等 | 构建可维护项目 |
十三、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!