TypeScript 泛型详解及应用场景
泛型(Generics)是 TypeScript 的核心特性,它允许我们编写可复用、类型安全的代码,同时保持灵活性。以下是深度解析和实际应用指南:
一、泛型基础概念
本质:参数化类型,将类型作为变量传递,像函数参数一样动态指定。
想象普通函数的参数是 值的变量,而泛型的参数是 类型的变量
-
T
就是一个类型变量,调用时才确定具体类型 -
就像函数运行时才接收具体值一样,泛型在调用时接收具体类型
// 定义泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用
const output1 = identity<string>("hello"); // 显式指定 T 为 string
const output2 = identity(42); // 自动推断 T 为 number
二、泛型核心应用场景
1. 函数/方法泛型
典型场景:处理多种类型输入但逻辑相同的函数。
// 返回数组第一项
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const str = firstElement(["a", "b"]); // T 推断为 string
const num = firstElement([1, 2]); // T 推断为 number
2. 接口/类型泛型
典型场景:定义可复用的数据结构模板。
interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
// 使用
type UserResponse = ApiResponse<{ name: string; age: number }>;
const res: UserResponse = {
code: 200,
data: { name: "Alice", age: 30 },
message: "success"
};
3. 类泛型
典型场景:可定制化的类实现。
class Box<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
getValue(): T {
return this.content;
}
}
const stringBox = new Box("hello");
const numberBox = new Box(42);
4. 约束泛型(Constraints)
典型场景:限制泛型参数必须满足某些条件。
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): void {
console.log(arg.length);
}
logLength("hello"); // OK,string 有 length 属性
logLength([1, 2]); // OK,数组有 length
logLength(42); // Error: number 没有 length
5. 默认泛型参数
典型场景:提供类型默认值。
interface Pagination<T = string> {
items: T[];
page: number;
}
const p1: Pagination = { items: ["a", "b"], page: 1 }; // T 默认为 string
const p2: Pagination<number> = { items: [1, 2], page: 1 };
三、高级泛型技巧
1. 条件类型(Conditional Types)
典型场景:根据输入类型动态决定输出类型。
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<number>; // false
四、实战应用案例
1. API 响应标准化
interface ApiResponse<T = any> {
success: boolean;
data: T;
error?: string;
}
async function fetchUser(): Promise<ApiResponse<{ id: number; name: string }>> {
const res = await axios.get("/user");
return res.data;
}
2
3. 工具类型实现
// 实现 Partial、Pick、Omit 等工具类型
type MyPartial<T> = { [P in keyof T]?: T[P] };
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
五、泛型最佳实践
-
避免过度泛型:只在真正需要灵活性的地方使用。
-
明确约束:用
extends
限制泛型范围,增强类型安全。 -
优先推断:尽量让 TypeScript 自动推断类型参数。
-
命名规范:泛型变量通常用
T
、U
、K
、V
等大写字母。
六、常见问题解答
Q1: 泛型会影响运行时性能吗?
A: 不会,泛型是编译期特性,会在编译后被擦除。
Q2: 泛型和 any 的区别?
A: 泛型保留类型信息,any
完全放弃类型检查。泛型是类型安全的。
Q3: 如何解决泛型推断失败?
A: 显式指定类型参数或调整函数签名。