TS中的泛型(generics)

目录

什么是泛型

为什么要有泛型

示例代码

泛型函数

泛型类

泛型的继承

泛型去继承一个类

泛型去继承一个接口


什么是泛型

在 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)

泛型函数

泛型函数其实就是在函数后面加上,T可以用任何类型都代替

// 泛型函数
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 方法进行打印操作。

通过泛型继承接口,我们可以在函数中对泛型类型进行更加精确的约束,以确保类型的一致性和兼容性。

你可能感兴趣的:(TS,服务器,typescript,前端,开发语言)