1

一、泛型的本质
泛型是 参数化类型 的一种实现方式,类似于函数中的参数传递,只不过这里传递的不是值,而是类型。
泛型的核心作用:
● 代码复用:通过对类型进行参数化,减少重复代码。
● 类型安全:避免使用 any 等宽松类型带来的隐患。
● 灵活性:在不固定具体类型的情况下,依然提供严格的类型检查。

  1. 泛型的核心特性
    示例:简单的泛型函数
function identity<T>(value: T): T {
    return value;
}

// 使用
const num = identity<number>(42); // 类型 T 推断为 number
const str = identity("Hello"); // 类型 T 自动推断为 string
特性解析:
● 类型参数化:T 是类型的占位符,只有在函数调用时才会确定其具体类型。
● 灵活性:T 可以是任何类型,但一旦确定,就会严格检查其一致性。

  1. 泛型在组件中的应用
    示例:一个支持多类型数据的列表组件
    在前端开发中,我们常需要实现通用的组件,比如一个支持显示任意数据类型的列表组件。

    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 提供了许多内置辅助类型,用于简化类型操作,以下是常用的几种:

  1. 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 允许我们构建可选的更新参数,避免要求提供完整的对象。

  2. 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" }); // 正确

  3. 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"; // 报错,不能修改只读属性
  4. 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
  5. 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
  6. 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: [],
    };

  7. 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"
  8. Awaited:提取 Promise 的返回值类型
    Awaited<T> 提取一个 Promise 的返回值类型,适用于异步场景。
    示例:封装异步函数

    type FetchResponse = Promise<{ data: string }>;
    type ResponseData = Awaited<FetchResponse>; // { data: string }

    三、高阶泛型的深度应用
    高阶泛型结合内置类型,可以在复杂场景下解决动态类型推断和操作问题。以下是一些真实项目中的应用示例。

  9. 动态推断表单类型
    在动态表单系统中,前端需要根据字段配置生成表单值的类型。结合 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,
    }; // 动态推断类型
  10. 提取组件的 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 }

  11. 通用 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)); // 类型安全

    四、高阶泛型:泛型能力的扩展
    高阶泛型是泛型与其他高级类型特性(如条件类型、映射类型、递归类型等)的结合,解决更复杂的类型设计问题。

  12. 条件泛型:动态类型逻辑
    条件泛型通过 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[]
  13. 映射泛型:动态生成类型结构
    映射泛型通过 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)。映射泛型可以快速为某些对象生成只读版本,从而确保数据不会被直接修改。

  14. 递归泛型:树形结构的动态定义
    递归泛型适合处理树形结构或动态嵌套的数据类型。
    示例:树状结构的建模

    interface TreeNode<T> {
     value: T;
     children?: TreeNode<T>[];
    }
    
    // 使用
    const tree: TreeNode<string> = {
     value: "root",
     children: [
         { value: "child1" },
         { value: "child2", children: [{ value: "grandchild" }] },
     ],
    };

    项目场景:递归处理动态表单
    在项目中,递归泛型可以用于动态生成嵌套表单的类型。例如,支持无限嵌套的表单配置。

  15. 动态推断泛型:从函数中提取类型
    动态推断泛型通过 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 的返回类型,以动态生成依赖注入的类型定义。

  16. 链式泛型:构建动态链式 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 请求封装
构建一个通用的请求封装工具,要求:

  1. 参数类型和返回值类型严格匹配。
  2. 动态支持不同的请求场景(如 GET、POST)。
  3. 对数据进行类型化转换。
    实现代码:

    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)); // 类型安全

    六、总结
    泛型与内置辅助类型的协作:

  4. 泛型 提供了灵活的类型参数化能力,适配不同的数据场景。
  5. 内置辅助类型 为复杂的类型操作提供了便捷。
  6. 高阶泛型 在结合辅助类型后,能够动态推断、转换或生成类型结构,解决复杂的业务需求。

会搭讪的苦咖啡
4 声望0 粉丝