在 TypeScript 中,泛型是一个强大的工具,它允许你在编写类、函数或接口时,保持代码的灵活性和类型安全。泛型使得类型在使用时才被确定,而不是在定义时就固定,从而提高了代码的复用性和可维护性。
简析泛型
1. 泛型的基础使用
泛型通常是定义类、函数或接口时,作为一个占位符,表示该位置会在使用时被具体的类型替换。下面是一个简单的泛型函数示例:
// 泛型函数:返回传入值的类型
function identity<T>(arg: T): T {
return arg;
}
let result1 = identity(42); // T 会被推断为 number
let result2 = identity("hello"); // T 会被推断为 string
在这个示例中,T 是一个类型参数,函数 identity 接受一个参数 arg,并返回相同类型的值。TypeScript 会自动推断 T 的类型,也可以显式地指定类型。
2. 泛型类
泛型类允许我们在定义类时使用类型参数。这种方式可以使得类的操作对象更加灵活,增强代码的复用性。
// 泛型类
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
}
let numberBox = new Box(123); // T 被推断为 number
let stringBox = new Box("hello"); // T 被推断为 string
console.log(numberBox.getValue()); // 输出 123
console.log(stringBox.getValue()); // 输出 hello
3. 泛型约束
有时我们希望泛型的类型参数能够满足某些条件,例如,类型参数必须拥有某个方法或者属性。可以通过 extends 关键字来为泛型添加约束。
// 泛型约束
interface LengthWise {
length: number;
}
function logLength<T extends LengthWise>(item: T): void {
console.log(item.length);
}
logLength("hello"); // 输出 5,因为字符串有 length 属性
logLength([1, 2, 3]); // 输出 3,因为数组有 length 属性
// logLength(42); // 错误,因为数字没有 length 属性
在这个示例中,T extends LengthWise 表示泛型 T 必须满足 LengthWise 接口的结构,即包含一个 length 属性。
4. 泛型与接口
接口也可以使用泛型,通常用于定义一种结构,其中类型会在使用时被具体化。泛型接口使得接口更加灵活。
// 泛型接口
interface Pair<T, U> {
first: T;
second: U;
}
let pair1: Pair<number, string> = { first: 1, second: "apple" };
let pair2: Pair<string, boolean> = { first: "hello", second: true };
在这里,Pair 接口接受两个类型参数 T 和 U,并用它们来定义 first 和 second 属性的类型。
5. 泛型约束与继承
通过组合接口、类与泛型约束,可以实现更复杂的类型逻辑。泛型类型不仅可以继承接口,还可以进行多层嵌套。
// 泛型约束与继承
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
}
function printAnimalName<T extends Animal>(animal: T): void {
console.log(animal.name);
}
let myDog = new Dog("Buddy", "Golden Retriever");
printAnimalName(myDog); // 输出 Buddy
在这个例子中,T 继承自 Animal 类,这确保了传入的参数一定具有 name 属性。
6. 高级泛型用法
条件类型
TypeScript 提供了条件类型,可以根据泛型的类型在编译时做出决策,进一步增强灵活性。
// 条件类型
type IsString<T> = T extends string ? "Yes" : "No";
type Test1 = IsString<string>; // "Yes"
type Test2 = IsString<number>; // "No"
映射类型
映射类型可以基于给定的类型生成一个新的类型。结合泛型,可以实现更加动态的类型定义。
// 映射类型
type ReadOnly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
type ReadOnlyPerson = ReadOnly<Person>;
// ReadOnlyPerson 的属性是只读的
7. 泛型的实际应用场景
- 数据结构:在实现栈、队列、链表等数据结构时,使用泛型可以确保元素类型的安全性。
- API 调用:在与后端接口交互时,使用泛型来定义 API 响应的类型,使得数据处理更加安全。
- 表单处理:对于动态表单,泛型可以根据表单字段类型定义动态表单值类型。
- 组件库:在实现组件库时,泛型可以根据组件的使用场景灵活地定义 props 和 state 的类型。
示例:API 调用与泛型
1. 定义 API 响应类型接口
我们首先定义几个接口,表示不同的 API 响应类型。
// 用户信息接口
interface User {
id: number;
name: string;
email: string;
}
// 商品信息接口
interface Product {
id: number;
name: string;
price: number;
stock: number;
}
// API 响应结构通用接口
interface ApiResponse<T> {
data: T;
message: string;
status: number;
}
User和Product是两个不同的 API 响应类型。ApiResponse<T>是一个通用接口,表示 API 响应的数据结构。T是泛型,表示响应数据的具体类型。
2. 创建泛型 API 调用函数
接下来,我们定义一个通用的 API 调用函数,接受泛型类型参数 T,并返回一个 Promise,该 Promise 会解析为 API 响应的数据。
// 模拟 API 调用
async function fetchApiData<T>(url: string): Promise<ApiResponse<T>> {
try {
const response = await fetch(url);
// 假设接口返回的是 JSON 格式
const result: ApiResponse<T> = await response.json();
if (response.ok) {
return result; // 返回 API 响应数据
} else {
throw new Error(result.message || '请求失败');
}
} catch (error) {
// 异常处理
console.error('API 调用错误:', error);
throw error;
}
}
在 fetchApiData 函数中,我们使用了 T 来表示 API 返回的数据类型。函数返回一个 Promise<ApiResponse<T>>,即一个 ApiResponse 对象,其中 data 字段的类型是 T。
3. 使用泛型 API 调用
现在我们可以根据不同的需求,调用 API 并解析返回的 JSON 数据。
// 获取用户信息
async function getUserInfo(userId: number) {
const url = `https://api.example.com/users/${userId}`;
const response = await fetchApiData<User>(url); // 传入 User 类型
console.log('用户信息:', response.data);
}
// 获取商品列表
async function getProductList() {
const url = 'https://api.example.com/products';
const response = await fetchApiData<Product[]>(url); // 传入 Product[] 类型
console.log('商品列表:', response.data);
}
// 示例:调用 API 获取数据
getUserInfo(123).catch(error => console.error(error));
getProductList().catch(error => console.error(error));