目录
什么是泛型
为什么要有泛型
示例代码
泛型函数
泛型类
泛型的继承
泛型去继承一个类
泛型去继承一个接口
在 TypeScript 中,泛型(Generics)是一种允许在定义类、接口和函数时,延迟指定其中某些类型的概念。泛型可以使代码更具有通用性和灵活性,因为它可以用于处理多种类型的数据。
泛型通过使用泛型参数(Generic parameter)来实现。泛型参数使用尖括号(<>)括起来放在类型的名称后面。这个参数可以在类、接口或函数内部代表任何类型。
泛型在编程中扮演着重要的角色,主要有以下几个原因:
1. 提高代码的灵活性和复用性:泛型允许我们编写通用的代码,能够适应多种数据类型。通过使用泛型,我们可以在类、接口和函数中定义使用参数化类型的逻辑,从而避免相同的代码重复编写。
2. 类型安全性:在使用泛型的情况下,编译器可以在编译时进行类型检查和推断。这意味着编译器可以确保数据的一致性以及正确的类型使用,减少程序在运行时发生类型错误的可能性。
3. 提高代码的可读性和可维护性:通过使用泛型,我们可以使代码更加清晰、易读和可维护。泛型参数可以提供有意义的类型名称,并且在代码中使用泛型的地方可以直观地表达意图。
4. 适应不同的数据类型:在处理不同类型的数据时,使用泛型可以确保代码的适应性和通用性。无论是处理字符串、数字、对象还是其他自定义类型,泛型可以在不修改代码的情况下适应这些类型的变化。
5. 避免类型断言和类型转换:使用泛型可以避免手动进行类型转换和类型断言的繁琐操作。泛型提供了类型推断和类型约束的功能,使得代码更加简洁和可维护。
总的来说,泛型使得我们可以编写更加灵活、可复用和类型安全的代码。它不仅提高了开发效率,还减少了错误和调试的成本。因此,在需要处理多种数据类型的情况下,使用泛型是非常有价值的。
function save(a:number):number {
return a;
}
let s = save(12)
// 现在这个函数功能比较单一,只能存数字,但是如果我们想可以让这个函数既能存数组又能存字符串,那么这个时候,我们首先想到的联合类型
function save(a:number|string):number|string {
return a;
}
let ss:number|string = save(12)
let sss:number|string = save('stt')
//如果用联合类型,那么我现在还想存Date、Biolean、自定义的User类型。那么咱们这个联合写不下去了。
// 所以咱么就想到一个比较极端的方法,我让这个函数的参数是一个any类型。
function save(a:any):any {
return a;
}
let b = save(12);
let c = save('str')
let d:number = save('ghirgh') // 也能过。any就相当于没有类型,一般不用any
正确写法,应该把类型当成参数传进去。也就是咱们一开始的时候,确定不了这个类型,但是用的时候,能够确定
// T只是一个占位符,可以用任何字母去表示
function save(a:T):T {
return a;
}
let s:string = save(12) // 编译不过,因为在调用这个函数的时候,传递了一个number,但是s是string类型的。
let ss = save('str'); // 可以通过类型推测,推测出是一个string类型
let sss = save(true)
泛型函数其实就是在函数后面加上
// 泛型函数
function joinArray(...args:T[][]):T[] {
let result:T[] = [];
args.forEach(arr => {
result = result.concat(arr)
})
return result;
}
// [1, 2, 3, 'a']
let arr = joinArray([1, 2, 3], ['a', 'b', 'c'])
console.log(arr)
class Box {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const numberBox = new Box(42);
console.log(numberBox.getValue()); // 输出: 42
const stringBox = new Box('Hello');
console.log(stringBox.getValue()); // 输出: 'Hello'
class Box {
value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
class NumberBox extends Box {
add(other: NumberBox): NumberBox {
const sum = this.value + other.getValue();
return new NumberBox(sum as T);
}
subtract(other: NumberBox): NumberBox {
const diff = this.value - other.getValue();
return new NumberBox(diff as T);
}
}
const number1 = new NumberBox(42);
const number2 = new NumberBox(10);
const result1 = number1.add(number2);
console.log(result1.getValue()); // 输出: 52
const result2 = number1.subtract(number2);
console.log(result2.getValue()); // 输出: 32
Box
类是一个泛型基类,NumberBox
类继承自 Box
类,并扩展了泛型参数的范围。NumberBox
类仅接受类型为 number
的泛型参数,因此在 add
和 subtract
方法中使用泛型参数 T
时,编译器可以确定 T
的实际类型,从而可以保证运算的正确性。
需要注意的是,当定义扩展泛型基类的类时,需要在类的名称后添加泛型参数的范围约束,以确保子类的类型参数满足基类的泛型参数约束。例如,在上述示例中,NumberBox
类继承自 Box
类型,泛型参数的范围约束为 T extends number
,表示子类的泛型参数必须是 number
类型或其子类型。
interface Printable {
print(): void;
}
function printItem(item: T): void {
item.print();
}
class Book implements Printable {
print(): void {
console.log("Printing book...");
}
}
class Magazine implements Printable {
print(): void {
console.log("Printing magazine...");
}
}
printItem(new Book()); // 输出: Printing book...
printItem(new Magazine()); // 输出: Printing magazine...
我们定义了一个名为 Printable
的接口,该接口包含一个 print
方法。然后,我们使用泛型继承了 Printable
接口,并在 printItem
函数中使用泛型参数 T
扩展了 Printable
接口。这样,我们可以确保传递给 printItem
函数的参数满足 Printable
接口的要求。
在函数调用中,我们分别传递了 Book
和 Magazine
类的实例作为参数。由于 Book
和 Magazine
类都实现了 Printable
接口,且满足 T extends Printable
泛型约束,所以它们可以传递给 printItem
函数,并且能够调用 print
方法进行打印操作。
通过泛型继承接口,我们可以在函数中对泛型类型进行更加精确的约束,以确保类型的一致性和兼容性。