阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 泛型设计模式

泛型设计模式

作者:陈川 阅读数:3908人阅读 分类: TypeScript

泛型设计模式的核心思想

泛型设计模式的核心在于编写可复用的代码组件,这些组件能够处理多种数据类型而不丧失类型安全性。TypeScript中的泛型允许我们创建灵活的函数、类和接口,同时保持严格的类型检查。泛型不是具体类型,而是类型参数,在使用时由开发者指定具体类型。

function identity<T>(arg: T): T {
    return arg;
}

let output1 = identity<string>("hello");  // 输出类型为string
let output2 = identity<number>(42);      // 输出类型为number

泛型约束与默认类型

泛型可以添加约束条件,限制可接受的类型范围。使用extends关键字可以指定类型参数必须满足的接口或基本类型。同时,TypeScript支持为泛型参数提供默认类型,当调用时未显式指定类型时会使用默认值。

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

// 必须传入带有length属性的对象
loggingIdentity({length: 10, value: 3});

// 设置默认类型
interface ApiResponse<T = any> {
    data: T;
    status: number;
}

const response: ApiResponse<string> = {
    data: "success",
    status: 200
};

泛型在函数中的应用

函数泛型可能是最常见的应用场景。它们允许我们编写能够处理多种类型输入的函数,同时保持输入和输出之间的类型关系。高阶函数特别适合使用泛型,因为它们经常需要处理各种类型的回调函数。

function map<T, U>(array: T[], transform: (item: T) => U): U[] {
    return array.map(transform);
}

const numbers = [1, 2, 3];
const strings = map(numbers, (n) => n.toString());  // string[]

function debounce<T extends (...args: any[]) => any>(
    func: T,
    wait: number
): (...args: Parameters<T>) => void {
    let timeout: number;
    return (...args: Parameters<T>) => {
        clearTimeout(timeout);
        timeout = setTimeout(() => func(...args), wait);
    };
}

泛型在类中的应用

类泛型允许我们创建可重用的类定义,这些类可以处理不同的数据类型。集合类(如栈、队列、链表)是泛型类的典型用例,因为它们本质上需要存储各种类型的元素。

class Stack<T> {
    private items: T[] = [];

    push(item: T): void {
        this.items.push(item);
    }

    pop(): T | undefined {
        return this.items.pop();
    }

    peek(): T | undefined {
        return this.items[this.items.length - 1];
    }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);

const stringStack = new Stack<string>();
stringStack.push("hello");

泛型在接口中的应用

接口泛型提供了一种定义灵活契约的方式。它们特别适合描述API响应、数据访问层或服务接口,因为这些场景通常需要处理各种数据类型但保持一致的形状。

interface Repository<T, ID> {
    findById(id: ID): Promise<T | null>;
    save(entity: T): Promise<T>;
    delete(id: ID): Promise<void>;
}

class UserRepository implements Repository<User, string> {
    async findById(id: string): Promise<User | null> {
        // 实现查找逻辑
    }
    // 其他方法实现...
}

interface PaginatedResponse<T> {
    data: T[];
    total: number;
    page: number;
    perPage: number;
}

async function fetchUsers(): Promise<PaginatedResponse<User>> {
    // 获取用户数据
}

条件类型与映射类型

TypeScript的高级类型特性如条件类型和映射类型与泛型结合使用时特别强大。条件类型允许基于类型关系选择类型,而映射类型可以转换现有类型的属性。

type NonNullable<T> = T extends null | undefined ? never : T;

type Partial<T> = {
    [P in keyof T]?: T[P];
};

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

// 使用示例
interface User {
    id: string;
    name: string;
    email: string;
}

type UserPreview = Pick<User, 'id' | 'name'>;

泛型工具类型实践

TypeScript内置了许多实用的泛型工具类型,合理使用它们可以显著减少重复的类型定义工作。这些工具类型通常结合了条件类型和映射类型的特性。

// 从T中排除可以赋值给U的类型
type Exclude<T, U> = T extends U ? never : T;

// 从T中提取可以赋值给U的类型
type Extract<T, U> = T extends U ? T : never;

// 获取函数返回类型
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

// 获取构造函数实例类型
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

// 实际应用
function getUser(): Promise<User> {
    return Promise.resolve({ id: '1', name: 'John' });
}

type UserPromise = ReturnType<typeof getUser>;  // Promise<User>

泛型与React组件

在React开发中,泛型组件可以创建高度可复用的UI元素。特别是对于列表、表格、表单等需要处理各种数据类型的组件,泛型提供了完美的解决方案。

interface ListProps<T> {
    items: T[];
    renderItem: (item: T) => React.ReactNode;
}

function GenericList<T>({ items, renderItem }: ListProps<T>) {
    return (
        <ul>
            {items.map((item, index) => (
                <li key={index}>{renderItem(item)}</li>
            ))}
        </ul>
    );
}

// 使用示例
const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];

<UserList 
    items={users}
    renderItem={(user) => <span>{user.name}</span>}
/>

泛型类型推断的最佳实践

TypeScript的类型推断系统非常强大,通常不需要显式指定泛型类型参数。理解类型推断的工作原理可以帮助我们编写更简洁的代码,同时保持类型安全。

// 不需要显式指定类型参数
let array = [1, 2, 3];  // 推断为number[]
let tuple = [1, "hello"] as const;  // 推断为readonly [1, "hello"]

// 函数参数类型推断
function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

const result = pair(1, "hello");  // 推断为[number, string]

// 回调函数参数类型推断
const users = [{ name: "Alice" }, { name: "Bob" }];
const names = users.map(user => user.name);  // 推断为string[]

泛型与函数重载的结合

当泛型与函数重载结合使用时,可以创建既灵活又具有精确类型签名的API。这种模式在库开发中特别有用,可以为不同的参数组合提供不同的返回类型。

function parseInput<T extends string | number>(input: T): T extends string ? Date : number;
function parseInput(input: string | number): Date | number {
    if (typeof input === 'string') {
        return new Date(input);
    } else {
        return input * 2;
    }
}

const date = parseInput("2023-01-01");  // Date
const number = parseInput(42);          // number

泛型在状态管理中的应用

状态管理库经常使用泛型来定义存储的结构和操作。Redux的reducer、selector和action创建函数都可以从泛型中受益,确保类型安全贯穿整个应用状态。

interface Action<T extends string, P> {
    type: T;
    payload: P;
}

function createAction<T extends string, P>(type: T, payload: P): Action<T, P> {
    return { type, payload };
}

// 使用示例
const addTodo = createAction('ADD_TODO', { text: 'Learn TypeScript' });

interface State {
    todos: Array<{ text: string; completed: boolean }>;
}

function reducer(state: State, action: Action<string, any>): State {
    switch (action.type) {
        case 'ADD_TODO':
            return {
                ...state,
                todos: [...state.todos, { text: action.payload.text, completed: false }]
            };
        // 其他case...
    }
}

泛型与高阶组件模式

在React中,高阶组件(HOC)经常使用泛型来保持被包装组件的props类型。这种模式确保了类型信息不会在组件组合过程中丢失。

function withLoading<T extends object>(
    WrappedComponent: React.ComponentType<T>
) {
    return function WithLoading(props: T & { isLoading: boolean }) {
        if (props.isLoading) {
            return <div>Loading...</div>;
        }
        return <WrappedComponent {...props} />;
    };
}

// 使用示例
interface UserProfileProps {
    user: User;
    className?: string;
}

const UserProfile: React.FC<UserProfileProps> = ({ user }) => (
    <div>{user.name}</div>
);

const UserProfileWithLoading = withLoading(UserProfile);

// 现在UserProfileWithLoading的props包括原来的UserProfileProps加上isLoading
<UserProfileWithLoading user={user} isLoading={true} />

泛型类型守卫与类型谓词

泛型可以与类型守卫结合使用,创建可重用的类型判断函数。这些函数不仅执行运行时检查,还帮助TypeScript编译器缩小类型范围。

function isArrayOf<T>(
    arr: unknown,
    check: (item: unknown) => item is T
): arr is T[] {
    return Array.isArray(arr) && arr.every(check);
}

function isString(item: unknown): item is string {
    return typeof item === 'string';
}

// 使用示例
const data: unknown = ["a", "b", "c"];

if (isArrayOf(data, isString)) {
    // 在此块中,data被推断为string[]
    data.map(s => s.toUpperCase());
}

泛型与索引访问类型

索引访问类型与泛型结合使用时,可以创建基于对象键的动态类型。这种模式在需要根据属性名访问或操作对象类型时特别有用。

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

// 使用示例
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name");  // string
const age = getProperty(user, "age");    // number

// 更复杂的例子
type EventMap = {
    click: MouseEvent;
    scroll: Event;
    keypress: KeyboardEvent;
};

function triggerEvent<T extends keyof EventMap>(
    eventName: T,
    handler: (event: EventMap[T]) => void
): void {
    // 模拟事件触发
    const event = {} as EventMap[T];
    handler(event);
}

triggerEvent("click", (e) => {
    // e被推断为MouseEvent
    console.log(e.clientX);
});

泛型与Promise组合模式

在处理异步操作时,泛型可以帮助我们保持Promise链中的类型一致性。特别是在构建API客户端或数据访问层时,这种模式可以确保从请求到响应整个流程的类型安全。

interface ApiResponse<T> {
    data: T;
    status: number;
    headers: Record<string, string>;
}

async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
    const response = await fetch(url);
    const data = await response.json();
    return {
        data,
        status: response.status,
        headers: Object.fromEntries(response.headers.entries())
    };
}

// 使用示例
interface User {
    id: number;
    name: string;
}

async function getUser() {
    const response = await fetchData<User[]>("/api/users");
    // response.data是User[]类型
    return response.data;
}

泛型与装饰器模式

在TypeScript中,装饰器可以与泛型结合使用,创建可重用的装饰器逻辑。这种模式在需要为不同类添加相同行为时特别有用,同时保持被装饰类的类型信息。

function LogClass<T extends { new (...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        constructor(...args: any[]) {
            super(...args);
            console.log(`Instance of ${constructor.name} created`);
        }
    };
}

// 使用示例
@LogClass
class UserService {
    constructor(private repository: UserRepository) {}
}

// 当实例化UserService时,会打印日志
const service = new UserService(new UserRepository());

泛型与可变元组类型

TypeScript 4.0引入的可变元组类型与泛型结合使用时,可以创建极其灵活的函数签名。这种模式特别适合需要处理可变参数列表的高阶函数。

function curry<T extends any[], U, R>(
    fn: (...args: [...T, U]) => R,
    ...initialArgs: T
): (arg: U) => R {
    return (arg: U) => fn(...initialArgs, arg);
}

// 使用示例
function add(a: number, b: number): number {
    return a + b;
}

const addFive = curry(add, 5);
const result = addFive(3);  // 8

// 更复杂的例子
function compose<T extends any[], U, V>(
    f: (x: U) => V,
    g: (...args: T) => U
): (...args: T) => V {
    return (...args) => f(g(...args));
}

const toUpperCase = (x: string) => x.toUpperCase();
const join = (separator: string, ...strings: string[]) => strings.join(separator);

const upperJoin = compose(toUpperCase, join);
const result2 = upperJoin("-", "a", "b", "c");  // "A-B-C"

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌