LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

TypeScript泛型:让类型也"通用"的魔法

zhenglin
2025年12月2日 9:20 本文热度 320

记得我刚学习TypeScript时,最让我头疼的就是泛型这个概念。什么TUK,看起来像密码一样神秘。但当我真正理解并开始使用泛型后,才发现它就像是TypeScript中的"瑞士军刀",能让我们的代码既灵活又类型安全。

今天,我想和大家分享我对于TypeScript泛型的理解和实战经验。


什么是泛型?从函数参数到类型参数

想象一下,如果你要写一个函数,它既能处理数字,又能处理字符串,还能处理任何其他类型,你会怎么做?

在JavaScript中,我们可能会这样写:

// JavaScript方式 - 缺乏类型安全

function identity(value) {

    return value;

}

const num = identity(42);        // 返回42,但类型信息丢失了

const str = identity("hello");   // 返回"hello",类型信息丢失了

而在TypeScript中,泛型给了我们更好的解决方案:

typescript:


// TypeScript泛型 - 保持类型安全

function identity<T>(value: T): T {

    return value;

}


const num = identity(42);        // 类型为 number

const str = identity("hello");   // 类型为 string

const bool = identity(true);     // 类型为 boolean

这里的<T>就是泛型参数,它像一个"类型变量",在函数被调用时确定具体的类型。

泛型基础:从简单到复杂

1. 泛型函数

让我们从一个实际的例子开始:

// 一个简单的栈实现

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];

    }

    

    size(): number {

        return this.items.length;

    }

}


// 使用示例 - 类型安全!

const numberStack = new Stack<number>();

numberStack.push(1);

numberStack.push(2);

// numberStack.push("hello"); // ❌ 编译错误:不能将字符串压入数字栈


const stringStack = new Stack<string>();

stringStack.push("hello");

stringStack.push("world");

2. 多个泛型参数

代码高亮:

// 处理键值对的函数

function pair<K, V>(key: K, value: V): [K, V] {

    return [key, value];

}


// 使用示例

const stringNumberPair = pair("age", 25);      // [string, number]

const numberBooleanPair = pair(1, true);       // [number, boolean]

const complexPair = pair("config", { debug: true }); // [string, { debug: boolean }]

3. 泛型约束

有时候,我们需要对泛型参数做一些限制:


// 要求泛型参数必须有length属性

interface HasLength {

    length: number;

}


function getLength<T extends HasLength>(item: T): number {

    return item.length;

}


// 使用示例

getLength("hello");        // ✅ 字符串有length

getLength([1, 2, 3]);      // ✅ 数组有length  

getLength({ length: 5 });  // ✅ 对象有length属性

// getLength(42);          // ❌ 数字没有length属性


实战场景:泛型在项目中的应用

场景1:API响应处理

在我的实际项目中,泛型在API层发挥了巨大作用


// 定义通用的API响应类型

interface ApiResponse<T> {

    success: boolean;

    data: T;

    message?: string;

    timestamp: number;

}


// 通用的API请求函数

async function apiRequest<T>(

    endpoint: string, 

    options?: RequestInit

): Promise<ApiResponse<T>> {

    const response = await fetch(`/api/${endpoint}`, options);

    const result: ApiResponse<T> = await response.json();

    return result;

}


// 定义具体的数据类型

interface User {

    id: number;

    name: string;

    email: string;

}


interface Product {

    id: number;

    title: string;

    price: number;

    category: string;

}


// 使用示例 - 完美的类型安全!

const userResponse = await apiRequest<User>("users/1");

console.log(userResponse.data.name);    // ✅ 正确的属性访问

// console.log(userResponse.data.invalid); // ❌ 编译错误


const productResponse = await apiRequest<Product>("products/123");

console.log(productResponse.data.price); // ✅ 正确的属性访问

场景2:工具函数库

泛型让工具函数变得更加通用和类型安全:

// 数组工具函数

function filterArray<T>(

    array: T[], 

    predicate: (item: T, index: number) => boolean

): T[] {

    return array.filter(predicate);

}


function mapArray<T, U>(

    array: T[], 

    mapper: (item: T, index: number) => U

): U[] {

    return array.map(mapper);

}


// 对象工具函数

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

    return obj[key];

}


function mergeObjects<T extends object, U extends object>(

    obj1: T, 

    obj2: U

): T & U {

    return { ...obj1, ...obj2 };

}


// 使用示例

const numbers = [1, 2, 3, 4, 5];

const evenNumbers = filterArray(numbers, n => n % 2 === 0); // number[]


const users = [

    { id: 1, name: "Alice" },

    { id: 2, name: "Bob" }

];

const userNames = mapArray(users, user => user.name); // string[]


const person = { name: "Alice", age: 30 };

const name = getProperty(person, "name"); // string

// const invalid = getProperty(person, "email"); // ❌ 编译错误

场景3:状态管理

在React项目中,泛型可以帮助我们创建类型安全的Hook:

代码高亮:

import { useState, useCallback } from 'react';


// 通用的表单Hook

function useForm<T extends Record<string, any>>(initialValues: T) {

    const [values, setValues] = useState<T>(initialValues);

    

    const setValue = useCallback(<K extends keyof T>(key: K, value: T[K]) => {

        setValues(prev => ({ ...prev, [key]: value }));

    }, []);

    

    const reset = useCallback(() => {

        setValues(initialValues);

    }, [initialValues]);

    

    return {

        values,

        setValue,

        reset,

        setValues

    };

}


// 使用示例

interface LoginForm {

    email: string;

    password: string;

    rememberMe: boolean;

}


function LoginComponent() {

    const { values, setValue } = useForm<LoginForm>({

        email: "",

        password: "", 

        rememberMe: false

    });

    

    // 完全类型安全!

    const handleEmailChange = (email: string) => {

        setValue("email", email); // ✅ 正确

    };

    

    const handleRememberChange = (remember: boolean) => {

        setValue("rememberMe", remember); // ✅ 正确

    };

    

    // setValue("invalidKey", "value"); // ❌ 编译错误

    // setValue("email", 123);          // ❌ 编译错误

}

场景4:高阶组件和渲染Props

// 带加载状态的高阶组件

function withLoading<TProps extends object>(

    Component: React.ComponentType<TProps>

) {

    return function WithLoadingComponent(props: TProps & { isLoading?: boolean }) {

        const { isLoading, ...componentProps } = props;

        

        if (isLoading) {

            return <div>Loading...</div>;

        }

        

        return <Component {...componentProps as TProps} />;

    };

}


// 数据获取组件

interface DataRendererProps<T> {

    url: string;

    children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode;

}


function DataRenderer<T>({ url, children }: DataRendererProps<T>) {

    const [data, setData] = useState<T | null>(null);

    const [loading, setLoading] = useState(true);

    const [error, setError] = useState<Error | null>(null);

    

    useEffect(() => {

        fetch(url)

            .then(response => response.json())

            .then((data: T) => {

                setData(data);

                setLoading(false);

            })

            .catch((err: Error) => {

                setError(err);

                setLoading(false);

            });

    }, [url]);

    

    return <>{children(data, loading, error)}</>;

}


// 使用示例

interface UserData {

    id: number;

    name: string;

    email: string;

}


function UserProfile() {

    return (

        <DataRenderer<UserData> url="/api/user/1">

            {(user, loading, error) => {

                if (loading) return <div>Loading user...</div>;

                if (error) return <div>Error: {error.message}</div>;

                if (user) return <div>Hello, {user.name}!</div>;

                return null;

            }}

        </DataRenderer>

    );

}


高级泛型技巧

1. 条件类型

// 根据条件选择类型

type IsString<T> = T extends string ? "yes" : "no";


type A = IsString<string>;    // "yes"

type B = IsString<number>;    // "no"


// 提取数组元素类型

type ArrayElement<T> = T extends (infer U)[] ? U : never;


type Numbers = ArrayElement<number[]>;      // number

type Strings = ArrayElement<string[]>;      // string

type Mixed = ArrayElement<(number | string)[]>; // number | string

2. 映射类型


// 让所有属性变为可选

type Partial<T> = {

    [P in keyof T]?: T[P];

};


// 让所有属性变为只读

type Readonly<T> = {

    readonly [P in keyof T]: T[P];

};


// 实际应用

interface User {

    id: number;

    name: string;

    email: string;

}


type PartialUser = Partial<User>;

// 等价于 { id?: number; name?: string; email?: string; }


type ReadonlyUser = Readonly<User>;

// 等价于 { readonly id: number; readonly name: string; readonly email: string; }

3. 泛型工具类型实战

代码高亮:

// 创建请求参数类型

interface ApiEndpoints {

    users: {

        GET: { id: number };

        POST: { name: string; email: string };

    };

    products: {

        GET: { category?: string };

        POST: { title: string; price: number };

    };

}


// 自动生成请求参数类型

type RequestParams<TEndpoint extends keyof ApiEndpoints, TMethod extends keyof ApiEndpoints[TEndpoint]> 

    = ApiEndpoints[TEndpoint][TMethod];


// 使用示例

type GetUserParams = RequestParams<"users", "GET">;     // { id: number }

type CreateUserParams = RequestParams<"users", "POST">; // { name: string; email: string }

type GetProductParams = RequestParams<"products", "GET">; // { category?: string }


常见陷阱和最佳实践

1. 不要过度使用泛型


// 不推荐:过度复杂的泛型

function overlyComplex<T extends Record<string, any>, K extends keyof T, U extends T[K]>(

    obj: T, 

    key: K, 

    transformer: (value: T[K]) => U

): U {

    return transformer(obj[key]);

}


// 推荐:保持简单

function getAndTransform<T, U>(

    obj: Record<string, T>,

    key: string,

    transformer: (value: T) => U

): U {

    return transformer(obj[key]);

}

2. 提供合理的默认值


// 为泛型参数提供默认值

interface PaginationOptions<T = any> {

    page: number;

    pageSize: number;

    filter?: (item: T) => boolean;

    sort?: (a: T, b: T) => number;

}


// 使用默认值

const defaultOptions: PaginationOptions = {

    page: 1,

    pageSize: 10

};


// 指定具体类型

const userOptions: PaginationOptions<User> = {

    page: 1,

    pageSize: 20,

    filter: user => user.active,

    sort: (a, b) => a.name.localeCompare(b.name)

};

3. 合理使用类型推断

// 让TypeScript自动推断类型

function createArray<T>(...items: T[]): T[] {

    return items;

}


// 自动推断为number[]

const numbers = createArray(1, 2, 3);

// 自动推断为string[] 

const strings = createArray("a", "b", "c");

// 自动推断为(string | number)[]

const mixed = createArray(1, "two", 3);


结语

泛型是TypeScript中最强大的特性之一,它让我们的代码在保持类型安全的同时,获得了极大的灵活性。从简单的工具函数到复杂的系统架构,泛型都能发挥重要作用。

记住学习泛型的关键:

  • ​从简单的用例开始,逐步深入

  • ​多实践,在真实项目中应用

  • ​不要害怕犯错,TypeScript编译器会指导你


泛型就像编程中的"魔法",一旦掌握,你就会发现它能解决很多之前觉得棘手的问题。希望今天的分享能帮助你在TypeScript的道路上更进一步!


参考文章:原文链接


该文章在 2025/12/2 9:20:30 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved