泛型与类型复用
🎯 引言
学会泛型,你就能写出更灵活、更可复用的前端代码。泛型让函数、接口和数组都能适应多种类型,避免重复造轮子。本文将带你从零开始了解泛型函数与接口,掌握泛型约束,还会介绍实用的工具类型,帮你写出干净又高效的代码。
🌀 泛型函数与接口:为类型插上“翅膀”
泛型最核心的作用就是类型参数化,简单来说,就是让代码里的类型像变量一样灵活传入。
泛型函数基础示例
function identity<T>(arg: T): T {
return arg;
}
const output1 = identity<string>('hello');
const output2 = identity(123); // 类型推断为 number
这里的 T 就像占位符,调用时传入具体类型。它让函数能处理任意类型,还保证类型安全。
泛型接口基础示例
接口中用泛型定义属性或者方法的类型,让接口更灵活:
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identityFn<T>(arg: T): T {
return arg;
}
const myIdentity: GenericIdentityFn<number> = identityFn;
GenericIdentityFn<T> 表示一个函数签名,接收 T 类型参数,返回 T 类型结果。这样写的接口,能适应多种类型的函数。
🔐 泛型约束:给泛型加个“安全带”
泛型虽然灵活,但有时需要限制类型,确保类型参数满足某个条件,避免出现没有预料的错误。
约束示例
假设我们想获取传入对象的某个属性值,但必须先确保对象有该属性:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity([1, 2, 3]); // 数组有 length,合法
// loggingIdentity(123); // 报错,number 没有 length
这里 T extends Lengthwise 表示 T 必须符合 Lengthwise 接口,即有 length 属性。这样就防止了在没有 length 的类型上调用 length。
🌈 泛型在函数、数组和接口中的应用
泛型的威力不仅限于简单函数和接口,下面我们用几个场景来加深理解。
泛型函数:处理多类型数据
function reverseArray<T>(items: T[]): T[] {
return items.reverse();
}
const nums = [1, 2, 3];
const reversedNums = reverseArray(nums); // 类型自动推断为 number[]
这个泛型函数能接受任意类型数组,返回相同类型的逆序数组。
泛型数组:声明灵活的数组类型
let strings: Array<string> = ['a', 'b', 'c'];
let numbers: Array<number> = [1, 2, 3];
Array<T> 是数组的泛型写法,声明数组元素类型,方便类型推断和代码提示。
泛型接口:为复杂数据建模
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
const response: ApiResponse<string[]> = {
data: ['apple', 'banana'],
status: 200,
message: 'Success',
};
用泛型接口包装不同类型的数据结构,提升代码复用。
🛠️ 简单介绍实用工具类型 Partial
TypeScript 提供了一些内置工具类型来方便类型操作,Partial<T> 是其中的一个常用类型。
Partial 的作用
它将类型 T 里的所有属性变成可选的,比如:
interface User {
id: number;
name: string;
age: number;
}
const updateUser: Partial<User> = {
name: 'Tom',
};
这样 updateUser 可以只写部分属性,特别适合做更新接口参数或组件 props。
Partial 的实现原理(简要)
type Partial<T> = {
[P in keyof T]?: T[P];
};
它用映射类型遍历 T 所有属性,把每个属性都变成可选的。
🔍 与 any 类型区别
TypeScript 中泛型(generics)和 any 的区别,虽然它们看起来都能「接受任意类型」,但本质和用途是完全不同的。
any 是 逃避类型检查
function log(input: any) {
console.log(input);
}
- input 可以是任何类型。
- 你完全失去了类型系统的帮助,没有类型检查、没有自动提示、没有错误提示。
any 只建议在 非常临时或无奈的场景下使用,比如处理第三方库返回的数据不确定时做个兜底。
泛型是类型占位符 + 类型安全
function log<T>(input: T): T {
console.log(input);
return input;
}
- T 是一个类型变量,调用的时候再指定(或者自动推导)。
- 类型检查仍然存在
- 可以根据传入的类型,推导出返回值的类型
- 有代码提示
🧾 小节总结
- 泛型让函数、接口等支持多种类型,实现代码复用和类型安全
- 泛型约束为泛型参数加限制,保证类型符合预期,避免运行错误
- 内置工具类型如
Partial<T>能简化类型操作,提升开发效率
❓ 知识问答(Q&A)
Q:泛型函数和普通函数有什么区别?
A:泛型函数使用类型参数,支持多种类型的输入输出;普通函数类型固定或不灵活。
Q:泛型约束的作用是什么?
A:限制泛型参数必须符合某个接口或类型,保证函数内部安全访问特定属性或方法。
Q:Partial
A:把类型 T 的所有属性变成可选,方便声明只需要部分属性的对象。
🧪 小练习
题目 1 :编写一个泛型函数,它可以接受一个数组和一个元素,将元素添加到数组末尾并返回新数组。
function appendToArray<T>(array: T[], element: T): T[] {
// 在这里实现
}
题目 2 :定义一个泛型接口 Result<T>,包含 success: boolean 和 data: T 两个属性,并用它来声明一个返回字符串数组的结果对象。
interface Result<T> {
// 定义属性
}
const res: Result<string[]> = {
// 填写属性值
};
🎉 恭喜你已经掌握泛型函数、接口、约束和实用工具类型 Partial
