泛型与函数重载
泛型的基本概念
泛型是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);
});
性能考量
虽然泛型和重载提供了强大的类型安全性,但也需要考虑编译时的性能影响。复杂的泛型类型和多重重载会增加类型检查的时间。对于性能敏感的项目,应该:
- 避免过度嵌套的泛型类型
- 限制重载签名的数量(通常不超过5-7个)
- 在热路径代码中考虑使用更简单的类型注解
// 可能影响性能的复杂泛型
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