一、泛型的本质
泛型是 参数化类型 的一种实现方式,类似于函数中的参数传递,只不过这里传递的不是值,而是类型。
泛型的核心作用:
● 代码复用:通过对类型进行参数化,减少重复代码。
● 类型安全:避免使用 any 等宽松类型带来的隐患。
● 灵活性:在不固定具体类型的情况下,依然提供严格的类型检查。
- 泛型的核心特性
示例:简单的泛型函数
function identity<T>(value: T): T {
return value;
}
// 使用
const num = identity<number>(42); // 类型 T 推断为 number
const str = identity("Hello"); // 类型 T 自动推断为 string
特性解析:
● 类型参数化:T 是类型的占位符,只有在函数调用时才会确定其具体类型。
● 灵活性:T 可以是任何类型,但一旦确定,就会严格检查其一致性。
泛型在组件中的应用
示例:一个支持多类型数据的列表组件
在前端开发中,我们常需要实现通用的组件,比如一个支持显示任意数据类型的列表组件。type 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>; } // 使用 const userList = [ { id: 1, name: "John" }, { id: 2, name: "Jane" }, ]; <List items={userList} renderItem={(user) => <span>{user.name}</span>} />;
特性解析:
● 灵活性:通过泛型参数 T,列表组件可以适配任意类型的 items。
● 类型安全性:renderItem 的 item 参数自动推断为 T 类型,避免类型错误。
二、TypeScript 内置辅助类型
TypeScript 提供了许多内置辅助类型,用于简化类型操作,以下是常用的几种:
Partial:将所有属性设为可选
Partial<T> 是 TypeScript 内置的辅助类型,用于将对象类型 T 的所有属性变为可选(?)。
示例:用于更新对象的工具函数type User = { id: number; name: string; email: string; }; function updateUser(user: User, updates: Partial<User>): User { return { ...user, ...updates }; }
// 使用
const user: User = { id: 1, name: "John", email: "john@example.com" };
const updatedUser = updateUser(user, { name: "Jane" }); // 更新 name
特性解析:
● Partial 允许我们构建可选的更新参数,避免要求提供完整的对象。Required:将所有属性设为必填
与 Partial 相反,Required<T> 将对象类型 T 的所有属性设为必填(移除 ?)。
示例:确保配置对象的完整性type Config = { url?: string; method?: "GET" | "POST"; };
// 使用 Required 确保完整性 function setup(config: Required<Config>) { console.log(config.url, config.method); }
// 报错:因为 url 和 method 必须提供
// setup({});
setup({ url: "/api", method: "POST" }); // 正确Readonly:将所有属性设为只读
Readonly<T> 用于生成一个只读的对象类型,防止对属性的修改。
示例:保护数据不被直接修改type Todo = { id: number; title: string; }; function freeze(todo: Todo): Readonly<Todo> { return Object.freeze(todo); } const todo = freeze({ id: 1, title: "Learn TypeScript" }); // todo.title = "Learn JS"; // 报错,不能修改只读属性
Pick:从类型中提取部分属性
Pick<T, K> 从类型 T 中选择 K 对应的属性组成一个新类型。
示例:控制 API 请求的返回数据type User = { id: number; name: string; email: string; }; type UserPreview = Pick<User, "id" | "name">; const userPreview: UserPreview = { id: 1, name: "John" }; // 仅包含 id 和 name
Omit:从类型中排除部分属性
Omit<T, K> 从类型 T 中移除 K 对应的属性组成一个新类型。
示例:创建安全的用户类型(移除敏感信息)type User = { id: number; name: string; email: string; password: string; }; type SafeUser = Omit<User, "password">; const safeUser: SafeUser = { id: 1, name: "John", email: "john@example.com" }; // 无 password
Record:构建键值对类型
Record<K, V> 用于构造一个以 K 为键、V 为值的对象类型。
示例:动态定义字典结构
type Role = "admin" | "user" | "guest";
type RolePermissions = Record<Role, string[]>;const permissions: RolePermissions = {
admin: ["read", "write", "delete"],
user: ["read"],
guest: [],
};Exclude 和 Extract:类型的集合操作
● Exclude<T, U> 从类型 T 中排除 U。
● Extract<T, U> 从类型 T 中提取 U。
示例:处理联合类型type All = "a" | "b" | "c"; type Excluded = Exclude<All, "a">; // "b" | "c" type Extracted = Extract<All, "a" | "c">; // "a" | "c"
Awaited:提取 Promise 的返回值类型
Awaited<T> 提取一个 Promise 的返回值类型,适用于异步场景。
示例:封装异步函数type FetchResponse = Promise<{ data: string }>; type ResponseData = Awaited<FetchResponse>; // { data: string }
三、高阶泛型的深度应用
高阶泛型结合内置类型,可以在复杂场景下解决动态类型推断和操作问题。以下是一些真实项目中的应用示例。动态推断表单类型
在动态表单系统中,前端需要根据字段配置生成表单值的类型。结合 Record 和 Partial,可以灵活实现动态表单的类型化。
实现:type FieldConfig = { [key: string]: "text" | "number" | "email"; }; type FormValues<T extends FieldConfig> = Partial<Record<keyof T, string | number>>; const fieldConfig = { name: "text", age: "number", email: "email", } as const; type Form = FormValues<typeof fieldConfig>; const form: Form = { name: "John", age: 30, }; // 动态推断类型
提取组件的 Props 类型
前端组件库中,动态提取组件的 Props 类型可以极大简化开发工作。
实现:type ComponentProps<T> = T extends React.ComponentType<infer P> ? P : never; type ButtonProps = ComponentProps<typeof Button>;
// 如果 Button 的 Props 定义为 { onClick: () => void; label: string }
// 则 ButtonProps 自动为 { onClick: () => void; label: string }通用 API 类型封装
高阶泛型如何结合内置类型封装灵活的 API 工具。
实现:type ApiResponse<T> = { data: T; error?: string; }; function createApiRequest<TParams, TResponse>(url: string) { return async (params: TParams): Promise<ApiResponse<TResponse>> => { const response = await fetch(url, { method: "POST", body: JSON.stringify(params), }); const data = await response.json(); return { data }; }; }
// 使用 const fetchUser = createApiRequest<{ id: number }, { name: string }>("/api/user"); fetchUser({ id: 1 }).then((res) => console.log(res.data.name)); // 类型安全
四、高阶泛型:泛型能力的扩展
高阶泛型是泛型与其他高级类型特性(如条件类型、映射类型、递归类型等)的结合,解决更复杂的类型设计问题。条件泛型:动态类型逻辑
条件泛型通过 extends 关键字实现类型层面的条件判断。
示例:区分数组与非数组type ArrayItem<T> = T extends Array<infer U> ? U : T; // 使用 type A = ArrayItem<number[]>; // A 是 number type B = ArrayItem<string>; // B 是 string
项目场景:API 返回值处理
在项目中,后端接口可能返回单一对象或数组对象,通过条件泛型,可以动态生成类型以适配两种情况。
type NormalizeResponse<T> = T extends Array<any> ? T : T[];// 使用 type SingleUser = { id: number; name: string }; type Normalized = NormalizeResponse<SingleUser>; // Normalized 为 SingleUser[]
映射泛型:动态生成类型结构
映射泛型通过 keyof 和映射特性,可以基于已有类型动态生成新的类型。
示例:为对象的所有字段添加修饰符type ReadonlyFields<T> = { readonly [K in keyof T]: T[K]; }; interface User { id: number; name: string; } type ReadonlyUser = ReadonlyFields<User>; // ReadonlyUser 为: // { // readonly id: number; // readonly name: string; // }
项目场景:状态管理中的不可变数据
在状态管理(如 Redux)中,数据通常需要不可变(immutable)。映射泛型可以快速为某些对象生成只读版本,从而确保数据不会被直接修改。递归泛型:树形结构的动态定义
递归泛型适合处理树形结构或动态嵌套的数据类型。
示例:树状结构的建模interface TreeNode<T> { value: T; children?: TreeNode<T>[]; } // 使用 const tree: TreeNode<string> = { value: "root", children: [ { value: "child1" }, { value: "child2", children: [{ value: "grandchild" }] }, ], };
项目场景:递归处理动态表单
在项目中,递归泛型可以用于动态生成嵌套表单的类型。例如,支持无限嵌套的表单配置。动态推断泛型:从函数中提取类型
动态推断泛型通过 infer 关键字,支持从复杂类型中提取信息。
示例:提取函数的参数与返回值类型type FunctionParams<T> = T extends (...args: infer P) => any ? P : never; type FunctionReturn<T> = T extends (...args: any[]) => infer R ? R : never; // 使用 type Params = FunctionParams<(a: string, b: number) => void>; // Params 为 [string, number] type Return = FunctionReturn<() => boolean>; // Return 为 boolean
项目场景:自动生成 Hook 的类型
在 React 开发中,可以通过此技术自动推断自定义 Hook 的返回类型,以动态生成依赖注入的类型定义。链式泛型:构建动态链式 API
链式泛型适用于需要链式调用的场景,比如配置构建器或动态规则生成器。
示例:动态构建器type Chainable<T = {}> = { option<K extends string, V>(key: K, value: V): Chainable<T & { [P in K]: V }>; get(): T; }; // 使用 const config = {} as Chainable() .option("name", "John") .option("age", 30) .get(); // config 类型为: // { // name: string; // age: number; // }
项目场景:动态表单配置生成
在复杂的前端应用中,动态表单的字段配置可以通过链式泛型实现,确保配置项的类型安全和灵活性。
五、综合案例:高阶泛型的深度应用
项目需求:通用 API 请求封装
构建一个通用的请求封装工具,要求:
- 参数类型和返回值类型严格匹配。
- 动态支持不同的请求场景(如 GET、POST)。
对数据进行类型化转换。
实现代码:interface RequestConfig<TParams, TResponse> { url: string; method: "GET" | "POST"; transformResponse?: (response: any) => TResponse; } function createRequest<TParams, TResponse>(config: RequestConfig<TParams, TResponse>) { return async (params: TParams): Promise<TResponse> => { const response = await fetch(config.url, { method: config.method, body: JSON.stringify(params), }); const data = await response.json(); return config.transformResponse ? config.transformResponse(data) : data; }; } // 使用 const fetchUser = createRequest<{ id: number }, { name: string }>({ url: "/api/user", method: "POST", transformResponse: (res) => ({ name: res.fullName }), }); fetchUser({ id: 1 }).then((user) => console.log(user.name)); // 类型安全
六、总结
泛型与内置辅助类型的协作:- 泛型 提供了灵活的类型参数化能力,适配不同的数据场景。
- 内置辅助类型 为复杂的类型操作提供了便捷。
- 高阶泛型 在结合辅助类型后,能够动态推断、转换或生成类型结构,解决复杂的业务需求。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。