阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 泛型与函数重载

泛型与函数重载

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

泛型的基本概念

泛型是TypeScript中用于创建可复用组件的核心工具。它允许开发者编写可以处理多种类型的代码,而不必为每种类型重复编写逻辑。泛型的本质是类型参数化,即在定义函数、接口或类时不预先指定具体类型,而是在使用时再指定。

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

let output1 = identity<string>("hello");
let output2 = identity<number>(42);

在这个例子中,T是类型变量,它可以捕获用户传入的类型。调用时,可以显式指定类型参数,也可以让TypeScript自动推断:

let output3 = identity("world");  // 类型推断为string
let output4 = identity(100);     // 类型推断为number

泛型约束

有时我们需要限制泛型参数的类型范围,这时可以使用extends关键字添加约束:

interface Lengthwise {
    length: number;
}

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

loggingIdentity("hello");  // 合法,字符串有length属性
loggingIdentity(3);       // 错误,数字没有length属性

还可以结合多个类型参数:

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

let x = { a: 1, b: 2 };
getProperty(x, "a");  // 合法
getProperty(x, "c");  // 错误,"c"不是x的属性

泛型接口与类

泛型也可以应用于接口和类:

interface GenericIdentityFn<T> {
    (arg: T): T;
}

let myIdentity: GenericIdentityFn<number> = identity;

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;

函数重载的基础

函数重载允许一个函数接受不同类型或数量的参数,并返回不同类型的结果。在TypeScript中,函数重载通过提供多个函数签名实现:

function reverse(value: string): string;
function reverse(value: number): number;
function reverse(value: string | number): string | number {
    if (typeof value === "string") {
        return value.split("").reverse().join("");
    }
    return Number(value.toString().split("").reverse().join(""));
}

reverse("hello");  // 返回"olleh"
reverse(123);      // 返回321

重载与泛型的结合

泛型和函数重载可以结合使用,创建更灵活的类型安全函数:

function combine<T>(a: T, b: T): T;
function combine<T, U>(a: T, b: U): [T, U];
function combine(a: any, b: any) {
    if (typeof a === typeof b) {
        return a + b;
    }
    return [a, b];
}

combine(1, 2);       // 返回3
combine("a", "b");   // 返回"ab"
combine(1, "a");     // 返回[1, "a"]

高级重载模式

对于更复杂的场景,可以使用条件类型和映射类型来增强重载:

type OverloadedReturn<T> = 
    T extends string ? number :
    T extends number ? string :
    never;

function transform<T extends string | number>(input: T): OverloadedReturn<T>;
function transform(input: any) {
    if (typeof input === "string") {
        return input.length;
    }
    return input.toString();
}

const strLength = transform("hello");  // number类型
const numStr = transform(42);         // string类型

实际应用场景

在处理API响应时,泛型和重载特别有用:

interface ApiResponse<T> {
    data: T;
    status: number;
}

function fetchData<T>(url: string): Promise<ApiResponse<T>>;
function fetchData<T>(url: string, callback: (data: T) => void): void;
function fetchData<T>(url: string, callback?: (data: T) => void): Promise<ApiResponse<T>> | void {
    const promise = fetch(url).then(res => res.json());
    if (callback) {
        promise.then(data => callback(data));
        return;
    }
    return promise;
}

// 使用方式1:Promise方式
fetchData<User>("/api/user").then(response => {
    console.log(response.data);
});

// 使用方式2:回调方式
fetchData<Product>("/api/product", product => {
    console.log(product.price);
});

性能考量

虽然泛型和重载提供了强大的类型安全性,但也需要考虑编译时的性能影响。复杂的泛型类型和多重重载会增加类型检查的时间。对于性能敏感的项目,应该:

  1. 避免过度嵌套的泛型类型
  2. 限制重载签名的数量(通常不超过5-7个)
  3. 在热路径代码中考虑使用更简单的类型注解
// 可能影响性能的复杂泛型
type DeepNested<T> = {
    [K in keyof T]: T[K] extends object ? DeepNested<T[K]> : T[K];
};

// 更简单的替代方案
interface Simplified {
    id: string;
    name: string;
    metadata?: Record<string, any>;
}

类型推断的边界

TypeScript的类型推断在处理泛型和重载时有明确边界。了解这些限制有助于编写更健壮的代码:

function firstElement<T>(arr: T[]): T {
    return arr[0];
}

// 自动推断为unknown[]
const result = firstElement([]);  

// 需要显式类型参数
const betterResult = firstElement<string>([]); 

对于重载函数,TypeScript会按顺序尝试每个重载签名,直到找到匹配的:

function createDate(timestamp: number): Date;
function createDate(year: number, month: number, day: number): Date;
function createDate(overload1: number, overload2?: number, overload3?: number): Date {
    if (overload2 !== undefined && overload3 !== undefined) {
        return new Date(overload1, overload2, overload3);
    }
    return new Date(overload1);
}

createDate(1640995200000);          // 使用第一个重载
createDate(2022, 0, 1);            // 使用第二个重载

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

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

上一篇:泛型类型推断

下一篇:泛型与继承

前端川

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