1.泛型介绍
泛型的主要作用是增强代码的可重用性、类型安全性和灵活性。通过使用泛型,我们可以编写更通用的代码,能够适应多种不同的数据类型,并且在编译阶段进行类型检查,减少潜在的运行时错误。在TS中,泛型可以应用于函数、类和接口。
2.泛型使用场景
2.1 数据结构的抽象化
使用泛型可以将数据结构中的类型信息参数化,使得数据结构在定义时不需要指定具体的数据类型,而是在使用时根据实际情况来确定。这样就可以编写通用的数据结构和算法,而不需要为每种特定类型都编写重复的代码,提高了代码的可重用性和灵活性。比如下面使用泛型来定义一个通用的栈数据结构,这样我们就可以利用其创建存储数字的栈和存储字符串的栈。
ts
class Stack<T> {
private items: T[] = [];
push(item: T) {
this.items.push(item);
}
pop() {
return this.items.pop();
}
}
// 创建一个存储数字的栈
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop()); // 输出:3
// 创建一个存储字符串的栈
const stringStack = new Stack<string>();
stringStack.push('Hello');
stringStack.push('World');
console.log(stringStack.pop()); // 输出:'World'
2.2 函数的灵活参数类型
使用泛型可以让函数适用于不同的参数类型,而不需要针对每种类型编写重复的代码,同时在编译阶段对函数的参数类型进行检查,有利于发现潜在类型问题。
ts
function reverse<T>(items: T[]): T[] {
return items.reverse();
}
const numbers = [1, 2, 3];
const reversedNumbers = reverse(numbers);
console.log(reversedNumbers); // 输出:[3, 2, 1]
const strings = ['a', 'b', 'c'];
const reversedStrings = reverse(strings);
console.log(reversedStrings); // 输出:['c', 'b', 'a']
2.3 接口泛型定义
接口泛型允许我们在接口的定义中使用参数化类型,以便在实现接口时动态地指定具体的类型,同时通过使用泛型定义接口,可以创建可扩展的抽象定义,比如与继承、联合类型等结合使用,从而实现更复杂的数据结构和功能。下面就是泛型接口和联合类型相结合,计算这两种图形面积和的例子。
ts
interface Shape {
getArea(): number;
}
interface Square<T> extends Shape {
sideLength: T;
}
interface Circle<T> extends Shape {
radius: T;
}
// 正方形和圆形的联合类型
type ShapeType = Square<number> | Circle<number>;
// 计算总面积
function calculateTotalArea(shapes: ShapeType[]): number {
let totalArea = 0;
for (let shape of shapes) {
totalArea += shape.getArea();
}
return totalArea;
}
// 计算正方形面积
class SquareImpl implements Square<number> {
constructor(public sideLength: number) {}
getArea(): number {
return this.sideLength * this.sideLength;
}
}
// 计算圆形面积
class CircleImpl implements Circle<number> {
constructor(public radius: number) {}
getArea(): number {
return Math.PI * this.radius * this.radius;
}
}
const square = new SquareImpl(5);
const circle = new CircleImpl(3);
const totalArea = calculateTotalArea([square, circle]);
console.log(totalArea); // 输出:84.53981633974483
2.4 泛型约束
通过使用泛型约束,可以对泛型类型参数进行类型约束,以使其满足特定的属性或行为要求,能够极大的增强类型的安全性。比如下面的泛型约束就是参入的参数必须有length属性且类型必须是数字。
ts
interface Lengthy {
length: number;
}
function printLength<T extends Lengthy>(obj: T): void {
console.log(obj.length);
}
printLength('Hello'); // 输出:5
printLength([1, 2, 3]); // 输出:3
3 总结
泛型通过类型约束为代码增加了灵活性和安全性。利用泛型可以在定义函数、类或接口时使用类型参数作为占位符,使得代码可以适用于不同的数据类型。通过给泛型添加类型约束可以限定泛型参数的类型范围,确保代码只能被特定类型的数据调用,从而避免潜在的类型错误。这种灵活性使得我们能够编写更通用、可重用的代码,同时在编译阶段就能发现并修复类型相关的错误,提高了代码的安全性和可维护性。通过使用泛型,我们能够更好地应对不同类型数据的需求,同时减少了重复代码的编写,增加了代码的灵活性和可读性。