TypeScript 类与泛型实践
一、泛型的基本概念
泛型是一种可以在定义函数、类或接口时使用的类型参数化工具。它允许我们编写可复用的代码,这些代码能够处理不同类型的数据,而不是局限于特定的类型。
二、泛型函数
- 简单的泛型函数示例
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("Hello"); // 明确指定类型参数为 string
console.log(output1);
let output2 = identity(123); // 类型推断,T 被推断为 number
console.log(output2);
在这个 identity 函数中,T 是一个泛型类型参数。函数接受一个类型为 T 的参数并返回相同类型的值。
2. 泛型函数的多个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
let result = pair<string, number>("Hello", 42);
console.log(result);
这里的 pair 函数有两个泛型类型参数 T 和 U,分别用于函数的两个参数,函数返回一个包含这两个不同类型值的元组。
三、泛型类
- 泛型类示例
class Box<T> {
private content: T;
constructor(item: T) {
this.content = item;
}
public getContent(): T {
return this.content;
}
}
let box1 = new Box<string>("Some text");
console.log(box1.getContent());
let box2 = new Box<number>(42);
console.log(box2.getContent());
Box 类是一个泛型类,T 作为类型参数。类中的 content 属性和 getContent 方法都使用了这个泛型类型,使得 Box 类可以存储和获取不同类型的数据。
四、泛型的使用场景
(一)数据结构操作
- 数组操作函数
function arrayMap<T, U>(arr: T[], callback: (item: T) => U): U[] {
return arr.map(callback);
}
let numbers = [1, 2, 3, 4];
let doubled = arrayMap<number, number>(numbers, (num) => num * 2);
console.log(doubled);
let names = ["Alice", "Bob", "Charlie"];
let nameLengths = arrayMap<string, number>(names, (name) => name.length);
console.log(nameLengths);
这个 arrayMap 函数可以对不同类型的数组进行映射操作,通过泛型类型参数 T 和 U 来适应不同类型的数组元素和映射结果类型。
(二)数据存储与获取
- 泛型缓存类
class Cache<T> {
private cache: { [key: string]: T } = {};
setItem(key: string, item: T): void {
this.cache[key] = item;
}
getItem(key: string): T | undefined {
return this.cache[key];
}
}
let stringCache = new Cache<string>();
stringCache.setItem("message", "Hello, Cache");
console.log(stringCache.getItem("message"));
let numberCache = new Cache<number>();
numberCache.setItem("count", 42);
console.log(numberCache.getItem("count"));
Cache 类可以用于缓存不同类型的数据,通过泛型 T 来确定缓存值的类型。
五、类型约束
(一)使用 extends 关键字进行类型约束
- 约束函数参数类型
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
let str = "Hello";
loggingIdentity(str);
let arr = [1, 2, 3];
loggingIdentity(arr);
// 错误示例:对象没有 length 属性
// let obj = { name: "John" };
// loggingIdentity(obj);
这里的 loggingIdentity 函数使用了类型约束,要求泛型类型 T 必须满足 Lengthwise 接口,即具有 length 属性。这样可以确保函数内部能够安全地访问 length 属性,增加了代码的安全性。
(二)在泛型类中使用类型约束
- 限制泛型类的类型参数
class SortedArray<T extends number | string> {
private array: T[] = [];
add(item: T): void {
// 假设这里有排序逻辑
this.array.push(item);
}
getArray(): T[] {
return this.array;
}
}
let numberArray = new SortedArray<number>();
numberArray.add(5);
numberArray.add(3);
console.log(numberArray.getArray());
let stringArray = new SortedArray<string>();
stringArray.add("Apple");
stringArray.add("Banana");
console.log(stringArray.getArray());
// 错误示例:不能使用布尔类型
// let boolArray = new SortedArray<boolean>();
// boolArray.add(true);
SortedArray 类限制了泛型类型参数 T 只能是 number 或 string 类型,这样在类的方法中操作 T 类型的数据时,可以更有针对性地编写代码,提高代码的灵活性和安全性。
通过合理地使用泛型和类型约束,我们可以在 TypeScript 中编写更加通用、灵活且安全的代码,减少代码的重复编写,提高代码的可维护性和可扩展性。