TypeScirpt类、泛型的使用实践记录:探讨TypeScript中的泛型的使用方法和场景,以及如何使用类型约束来增加代码的灵活性和安全性。
在 TypeScript 中, 泛型(Generics) 是一种强大的工具,用于提高代码的灵活性和可复用性,同时确保类型安全。
泛型 允许我们在定义函数、类或接口时,指定类型参数,而不是写死类型,这样在使用时可以根据具体情况传入不同的类型,从而避免重复的代码并增加灵活性。
1. 泛型的基本用法
泛型的基本语法是通过 尖括号(<>)定义一个类型参数,在函数、接口或类中使用这些类型参数。
示例:泛型函数
// 泛型函数
function identity<T>(value: T): T {
return value;
}
let num = identity(42); // num 的类型为 number
let str = identity("Hello"); // str 的类型为 string
在这个例子中,<T> 是一个类型参数,可以代表任何类型。当调用 identity 函数时,TypeScript 会推断出传入值的类型,并将其作为 T 来使用。
示例:泛型接口
interface Box<T> {
value: T;
}
let numberBox: Box<number> = { value: 42 };
let stringBox: Box<string> = { value: "Hello" };
这里,Box 是一个泛型接口,它定义了一个 value 属性,类型为 T,可以根据需要创建不同类型的 Box。
2. 泛型在类中的使用
泛型不仅可以用于 函数 和 接口,也可以用于 类。
class GenericStack<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];
}
}
const numberStack = new GenericStack<number>();
numberStack.push(10);
numberStack.push(20);
const stringStack = new GenericStack<string>();
stringStack.push("Hello");
stringStack.push("World");
在这个例子中,GenericStack 类是一个泛型类,它的 push 和 pop 方法都可以接受任何类型的元素。
3. 泛型约束(Type Constraints)
类型约束:有时候我们希望将泛型的类型参数限制为某些类型(例如,限制为实现了某个接口的类型)。这时可以使用 类型约束。
示例:限制泛型类型
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(value: T): void {
console.log(value.length);
}
logLength([1, 2, 3]); // 数组具有 length 属性
logLength("Hello"); // 字符串具有 length 属性
logLength({ length: 10 }); // 自定义对象,具有 length 属性
// 下面的会报错,因为数字类型没有 length 属性
// logLength(42);
在这个例子中,T extends Lengthwise 表示类型 T 必须包含 length 属性。通过使用泛型约束,我们保证了在 logLength 函数中调用 .length 时,它确实是存在的。
4. 默认泛型类型
TypeScript 允许为泛型提供默认类型。这意味着如果调用函数或类时没有传入具体类型,TypeScript 会使用默认类型。
function createArray<T = number>(length: number, value: T): T[] {
return new Array(length).fill(value);
}
let arr1 = createArray(3, 42); // T 默认是 number
let arr2 = createArray<string>(3, "Hello"); // T 被显式指定为 string
在这个例子中,function createArray<T = number>(length: number, value: T): 表示类型 T 默认类型为 number类型。
5. 多个泛型参数
TypeScript 允许一个函数、类或接口使用多个泛型参数。
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
let result = swap([1, "one"]); // result 的类型是 [string, number]
6. 泛型与联合类型
泛型也可以与联合类型一起使用,这使得代码更具灵活性。
function combine<T, U>(a: T, b: U): T | U {
return Math.random() > 0.5 ? a : b;
}
let result = combine(10, "Hello"); // result 的类型是 number | string
7. 泛型在高阶函数中的应用
泛型特别适用于高阶函数,这些函数接收另一个函数作为参数 或 返回另一个函数。使用泛型时,我们可以确保类型的传递和返回的一致性。
function withLogging<T>(fn: (...args: any[]) => T): (...args: any[]) => T {
return function (...args: any[]) {
console.log("Arguments:", args);
return fn(...args);
};
}
const add = (a: number, b: number): number => a + b;
const loggedAdd = withLogging(add);
loggedAdd(1, 2); // 输出: Arguments: [1, 2]
8. 泛型的应用场景
- 集合类和容器类:如数组、链表、栈、队列等容器类型,可以使用泛型来确保元素的类型一致性。
- 工具函数:在处理不同类型的数据时,使用泛型函数可以避免重复编写类似功能的代码,增加代码复用性。
- API 返回类型:根据不同的请求,API 可以返回不同类型的数据,通过泛型确保返回的数据类型与请求匹配。
- React 组件:React 中的组件(尤其是函数组件)可以使用泛型来接受不同类型的 props。
总结
- 通过使用泛型,TypeScript 使得代码更加灵活和可复用。
- 使用类型约束可以保证代码的类型安全,避免类型错误。
- 泛型使得类型推导更加精确,避免了硬编码的类型,降低了出错的几率。
泛型的使用不仅限于函数,还可以扩展到接口、类、类型别名等,提供了丰富的应用场景来提升代码的可维护性和安全性。