目录
在 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<T>(a:T):T {
return a;
}
let s:string = save<number>(12) // 编译不过,因为在调用这个函数的时候,传递了一个number,但是s是string类型的。
let ss = save('str'); // 可以通过类型推测,推测出是一个string类型
let sss = save(true)
泛型函数其实就是在函数后面加上<T>,T可以用任何类型都代替
// 泛型函数
function joinArray<T>(...args:T[][]):T[] {
let result:T[] = [];
args.forEach(arr => {
result = result.concat(arr)
})
return result;
}
// [1, 2, 3, 'a']
let arr = joinArray<number| string>([1, 2, 3], ['a', 'b', 'c'])
console.log(arr)
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const numberBox = new Box<number>(42);
console.log(numberBox.getValue()); // 输出: 42
const stringBox = new Box<string>('Hello');
console.log(stringBox.getValue()); // 输出: 'Hello'
class Box<T> {
value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
class NumberBox<T extends number> extends Box<T> {
add(other: NumberBox<T>): NumberBox<T> {
const sum = this.value + other.getValue();
return new NumberBox<T>(sum as T);
}
subtract(other: NumberBox<T>): NumberBox<T> {
const diff = this.value - other.getValue();
return new NumberBox<T>(diff as T);
}
}
const number1 = new NumberBox<number>(42);
const number2 = new NumberBox<number>(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<number>
?类型,泛型参数的范围约束为?T extends number
,表示子类的泛型参数必须是?number
?类型或其子类型。
interface Printable {
print(): void;
}
function printItem<T extends Printable>(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
?方法进行打印操作。
通过泛型继承接口,我们可以在函数中对泛型类型进行更加精确的约束,以确保类型的一致性和兼容性。