理解泛型:类型系统中的"变量"
graph TD
A[泛型概念] --> B[基本语法]
A --> C[核心价值]
C --> D[代码重用]
C --> E[类型安全]
C --> F[灵活性]
B --> G[泛型函数]
B --> H[泛型接口]
B --> I[泛型类]
想象你正在开发一个函数,既可以处理数字,又可以处理字符串。没有泛型时,你可能会写出:
// 非泛型实现 - 冗余代码
function identityNumber(arg: number): number {
return arg;
}
function identityString(arg: string): string {
return arg;
}
泛型提供了类型变量,让你可以创建可重用的组件:
// 泛型实现 - 单一函数处理多种类型
function identity<T>(arg: T): T {
return arg;
}
const str = identity<string>("TypeScript"); // string 类型
const num = identity<number>(42); // number 类型
const arr = identity<number[]>([1, 2, 3]); // number[] 类型
这里T就是一个类型变量,它捕获用户传入的类型,允许我们在函数内部使用这个类型。
泛型的核心价值
1. 代码复用性
- 消除重复代码逻辑
- 单一实现服务多种类型
2. 类型安全性
- 编译时类型检查
- 避免运行时类型错误
3. 灵活性
- 保留类型信息
- 支持复杂类型操作
泛型的应用场景
1. 泛型函数
// 返回数组第一个元素
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const numbers = firstElement([1, 2, 3]); // number | undefined
const strings = firstElement(["a", "b", "c"]); // string | undefined
2. 泛型接口
interface KeyValuePair<K, V> {
key: K;
value: V;
}
const stringPair: KeyValuePair<string, string> = {
key: "username",
value: "tsUser"
};
const numberPair: KeyValuePair<number, boolean> = {
key: 1,
value: true
};
3. 泛型类
class Box<T> {
private content: T;
constructor(initialValue: T) {
this.content = initialValue;
}
getValue(): T {
return this.content;
}
setValue(newValue: T): void {
this.content = newValue;
}
}
const stringBox = new Box<string>("Hello Generics");
console.log(stringBox.getValue().toUpperCase()); // "HELLO GENERICS"
const numberBox = new Box<number>(42);
console.log(numberBox.getValue().toFixed(2)); // "42.00"
高级泛型技巧
graph TD
J[高级技巧] --> K[泛型约束]
J --> L[泛型参数默认值]
J --> M[条件类型]
J --> N[映射类型]
1. 泛型约束(Generic Constraints)
使用extends关键字约束泛型类型:
// 约束为具有length属性的类型
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(`长度:${arg.length}`);
return arg;
}
logLength("TS"); // 正确 - 字符串有length
logLength([1, 2, 3]); // 正确 - 数组有length
logLength(42); // 错误 - 数字没有length
2. 在泛型约束中使用类型参数
// 确保key存在于对象中
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const user = { name: "Alice", age: 30 };
getProperty(user, "name"); // 正确
getProperty(user, "email"); // 错误 - 'email' 不在 user 的属性中
3. 泛型参数默认值
// 设置默认的容器类型为 HTMLElement
function createContainer<T = HTMLElement>(tagName: string): T {
return document.createElement(tagName) as T;
}
const div = createContainer("div"); // HTMLElement
const myDiv = createContainer<HTMLDivElement>("div"); // 指定为更具体的类型
4. 类型推断与泛型
TypeScript可以自动推断泛型类型:
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
// 类型自动推断为 [string, number]
const result = pair("TypeScript", 4.2);
实用泛型工具类型
TypeScript内置了强大的泛型工具类型:
// Partial: 所有属性变为可选
type PartialUser = Partial<{ name: string; age: number }>;
// { name?: string; age?: number }
// Readonly: 所有属性变为只读
type ReadonlyUser = Readonly<{ name: string }>;
// { readonly name: string }
// Pick: 选择对象类型的子集
type NameOnly = Pick<{ name: string; age: number }, "name">;
// { name: string }
// Omit: 移除对象类型的部分属性
type WithoutAge = Omit<{ name: string; age: number }, "age">;
// { name: string }
// Record: 创建键值映射
type UserRoles = Record<"admin" | "user", boolean>;
// { admin: boolean; user: boolean }
// ReturnType: 获取函数返回类型
type FetchResponse = ReturnType<typeof fetch>;
// Promise<Response>
条件类型与映射类型
条件类型
根据条件选择类型:
// 如果 T 是数组,返回数组元素类型,否则返回 T 本身
type Flatten<T> = T extends Array<infer U> ? U : T;
// string 类型
type StringType = Flatten<string>;
// number 类型
type NumberType = Flatten<number[]>;
映射类型
对现有类型进行变换:
// 将所有属性改为可空
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
// 为所有属性添加 getter 函数
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
type UserInfo = {
name: string;
age: number;
};
type UserGetters = Getters<UserInfo>;
// { getName: () => string; getAge: () => number }
实际应用场景
1. API响应处理
interface ApiResponse<T> {
code: number;
message: string;
data: T;
timestamp: Date;
}
// 用户数据响应
type UserApiResponse = ApiResponse<{
id: number;
email: string;
role: "user" | "admin";
}>
// 产品数据响应
type ProductApiResponse = ApiResponse<{
id: number;
name: string;
price: number;
stock: number;
}>
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
return await response.json();
}
// 使用时明确指定返回类型
const userData = await fetchData<UserApiResponse["data"]>("/api/user");
console.log(userData.email); // 类型安全访问
2. 高级函数组合
// 函数组合类型
type Compose<A, B, C> = (f: (b: B) => C, g: (a: A) => B) => (a: A) => C;
// 实现组合函数
const compose: Compose<unknown, unknown, unknown> =
(f, g) => (a) => f(g(a));
// 使用组合函数
const toUpperCase = (s: string) => s.toUpperCase();
const addExclamation = (s: string) => s + "!";
const shout = compose(toUpperCase, addExclamation);
console.log(shout("hello")); // "HELLO!"
3. 类型安全的Redux Reducer
// 定义Action基础结构
interface Action<T extends string> {
type: T;
}
// 带有payload的Action
interface PayloadedAction<T extends string, P> extends Action<T> {
payload: P;
}
// 创建Action Creator
function createAction<T extends string>(type: T): Action<T>;
function createAction<T extends string, P>(type: T, payload: P): PayloadedAction<T, P>;
function createAction<T extends string, P>(type: T, payload?: P) {
return payload ? { type, payload } : { type };
}
// 使用示例
const loginSuccess = createAction("LOGIN_SUCCESS", { username: "tsMaster" });
const logout = createAction("LOGOUT");
泛型最佳实践
-
使用描述性类型变量名
// 优先使用 function save<Entity>(entity: Entity): Promise<Entity> // 避免 function save<T>(entity: T): Promise<T> -
合理约束泛型参数
// 添加约束提供类型安全 function getById<Model extends { id: string }>(id: string): Model -
避免过度使用泛型
- 当具体类型足够时不要使用泛型
- 避免过度复杂的类型关系
-
利用类型推断减少类型参数
// 让TypeScript自动推断 const users = new Array<User>(); -
优先使用内置工具类型
- 避免重复造轮子
- 使用Partial、Pick、Omit等简化代码
泛型与性能
在TypeScript中使用泛型对运行时性能没有影响,因为泛型是编译时特性。编译后所有类型信息都会被擦除:
// TypeScript 代码
function identity<T>(arg: T): T {
return arg;
}
// 编译后的JavaScript
function identity(arg) {
return arg;
}
泛型是TypeScript最强大的特性之一,它提供了:
- 类型抽象能力:创建灵活可复用的代码组件
- 编译时类型安全:在开发阶段捕捉潜在错误
- 开发效率提升:减少重复代码并增强IDE支持
- 类型表达能力:精确描述复杂的数据结构和算法
掌握泛型将使你能够:
- 构建类型安全的API和框架
- 创建复杂的类型转换和操作
- 提高代码的可维护性和可扩展性
- 深入理解TypeScript的类型系统
随着TypeScript版本迭代,泛型的能力不断增强(如条件类型、模板字面量类型等),成为现代TypeScript开发不可或缺的核心技术。