什么是泛型,泛型在实际项目中如何高度使用?

242 阅读8分钟

一、泛型的本质

泛型是 参数化类型 的一种实现方式,类似于函数中的参数传递,只不过这里传递的不是值,而是类型。

泛型的核心作用:

  • 代码复用:通过对类型进行参数化,减少重复代码。
  • 类型安全:避免使用 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 可以是任何类型,但一旦确定,就会严格检查其一致性。

2. 泛型在组件中的应用

示例:一个支持多类型数据的列表组件

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

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
  • 类型安全性renderItemitem 参数自动推断为 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. ExcludeExtract:类型的集合操作

  • 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 }

三、高阶泛型的深度应用

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


1. 动态推断表单类型

在动态表单系统中,前端需要根据字段配置生成表单值的类型。结合 RecordPartial,可以灵活实现动态表单的类型化。

实现:

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,
}; // 动态推断类型

2. 提取组件的 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 }

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

四、高阶泛型:泛型能力的扩展

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


1. 条件泛型:动态类型逻辑

条件泛型通过 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[]

2. 映射泛型:动态生成类型结构

映射泛型通过 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)。映射泛型可以快速为某些对象生成只读版本,从而确保数据不会被直接修改。


3. 递归泛型:树形结构的动态定义

递归泛型适合处理树形结构或动态嵌套的数据类型。

示例:树状结构的建模

interface TreeNode<T> {
    value: T;
    children?: TreeNode<T>[];
}

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

项目场景:递归处理动态表单

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


4. 动态推断泛型:从函数中提取类型

动态推断泛型通过 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 的返回类型,以动态生成依赖注入的类型定义。


5. 链式泛型:构建动态链式 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)); // 类型安全

六、总结

泛型与内置辅助类型的协作:

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