TypeScript 中的泛型是一种强大的工具,它可以在编写代码时提供更好的类型安全性和代码重用性。下面我将探讨一些 TypeScript 中泛型的使用方法和场景,以及如何使用类型约束来增加代码的灵活性和安全性。
1. 简单的泛型函数
假设我们有一个简单的需求,编写一个函数,接受一个数组,并返回数组中的第一个元素。我们可以使用泛型来实现这个函数:
function getFirstElement<T>(arr: T[]): T | undefined {
return arr.length > 0 ? arr[0] : undefined;
}
const numbers = [1, 2, 3, 4, 5];
const firstNumber = getFirstElement(numbers); // 类型推断为 number | undefined
const strings = ['a', 'b', 'c'];
const firstString = getFirstElement(strings); // 类型推断为 string | undefined
2. 泛型约束
有时候我们想要对泛型进行一些约束,以确保泛型类型具有某些属性或方法。这可以通过使用泛型约束来实现。例如,我们想编写一个函数来查找数组中的最大值:
function findMaxValue<T extends number>(arr: T[]): T | undefined {
if (arr.length === 0) {
return undefined;
}
let max = arr[0];
for (const val of arr) {
if (val > max) {
max = val;
}
}
return max;
}
const numbers = [3, 7, 2, 8, 5];
const maxValue = findMaxValue(numbers); // 类型推断为 number | undefined
在上面的例子中,我们使用了 extends number 泛型约束,这意味着泛型类型必须是 number 类型或其子类型。
3. 泛型类
我们不仅可以在函数中使用泛型,还可以在类中使用泛型。例如,考虑一个简单的栈(堆栈)类:
class Stack<T> {
private items: T[] = [];
push(item: T) {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
const poppedNumber = numberStack.pop(); // 类型推断为 number | undefined
const stringStack = new Stack<string>();
stringStack.push('a');
stringStack.push('b');
stringStack.push('c');
const poppedString = stringStack.pop(); // 类型推断为 string | undefined
4. 在接口中使用泛型
泛型不仅可以在函数和类中使用,还可以在接口中使用。这使我们能够创建具有灵活类型的接口定义。例如,考虑一个简单的键值对接口:
interface KeyValuePair<K, V> {
key: K;
value: V;
}
const pair: KeyValuePair<string, number> = { key: 'age', value: 25 };
5. 默认泛型类型
在某些情况下,我们可能希望为泛型类型提供默认值。这可以通过在泛型声明中使用默认类型来实现:
function identity<T = any>(value: T): T {
return value;
}
const result = identity(42); // 类型推断为 number
6. 结论
TypeScript 的泛型功能使得我们能够编写更加灵活、安全且可重用的代码。通过泛型,我们可以在编译时捕获许多类型错误,并在多种情况下实现通用的解决方案。无论是函数、类、接口还是类型约束,泛型都可以在各种场景中发挥重要作用。