【TypeScript】泛型

2024-01-08 02:25:46

1、介绍

定义:
泛型是指在定义函数、接口或类的时候,不预先指定具体类型,而是在使用的时候再指定类型

语法:
泛型的语法是 <> 里写类型参数,一般可以用 T 来表示
泛型中的 T 就像一个占位符、或者说一个变量,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出

泛型的好处:

  • 函数和类可以轻松地支持多种类型,增强程序的拓展性
  • 不必写冗长的联合类型,增强代码的可读性
  • 灵活控制类型之间的约束

为什么需要泛型?
不使用泛型:

function identity(arg: number): number {
    return arg;
}

function identity(arg: string): string {
    return arg;
}

或者使用any类型来定义函数:

function identity(arg: any): any {
    return arg;
}

使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的
因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的

2、泛型函数

- 定义泛型

function identity<T>(arg: T): T {
    return arg;
}
// T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型

- 使用泛型

定义了泛型函数后,两种方法使用
(1)传入所有的参数,包含类型参数:

let output = identity<string>("myString"); // type of output will be 'string'

(2)利用了类型推论 – 即编译器会根据传入的参数自动地帮助我们确定T的类型:

let output = identity("myString");  // type of output will be 'string'

3、泛型接口

泛型接口允许你在定义接口的时候不具体指明某些类型,并且在实现接口或者使用接口的时候再指定这些类型
泛型接口可以用来约束对象的形状、数组内容、函数签名等多种用途。

3.1 约束对象形状:

定义:

interface Identity<T> {
  value: T;
  getMessage(): string;
}

使用:

// 使用接口并指定 T 为 number 类型
let numberIdentity: Identity<number> = {
  value: 42,
  getMessage() {
    return `Value is a number: ${this.value}`;
  }
};

// 使用接口并指定 T 为 string 类型
let stringIdentity: Identity<string> = {
  value: 'Hello',
  getMessage() {
    return `Value is a string: ${this.value}`;
  }
};

3.2 泛型接口作为函数类型

定义:

interface GenericFn<T> {
  (arg: T): T;
}

使用:

// 实现接口的函数 - 返回值类型与入参数类型相同
const identity: GenericFn<number> = (arg) => arg;

3.3 泛型接口作为字典的类型

定义:

interface Dictionary<T> {
  [key: string]: T;
}

使用:

// 字典创建时确定其值应为字符串类型
let keys: Dictionary<string> = {
  key1: 'value1',
  key2:'value2',
};

// 字典创建时确定其值应为数组类型
let objectList: Dictionary<object[]> = {
  list1: [{}, {}],
  list2: [{}, {}, {}]
};

3.4 泛型接口与多个类型参数

定义:

interface Pair<K, V> {
  key: K;
  value: V;
}

使用:

let pair: Pair<string, number> = {
  key: 'age',
  value: 35
};

4、泛型类

泛型类是具有一个或多个泛型类型参数的类,这些类型参数在类实例化的时候被指定

4.1 定义一个泛型类

定义:

class GenericClass<T> {
  // property 的类型为 T
  value: T;

  // 构造函数参数的类型为 T
  constructor(value: T) {
    this.value = value;
  }

  // 方法返回值的类型为 T
  getValue(): T {
    return this.value;
  }
}

使用:

// 使用字符串类型实例化类
const stringInstance = new GenericClass<string>('Hello, TypeScript!');
console.log(stringInstance.getValue()); // 输出: Hello, TypeScript!

// 使用数字类型实例化类
const numberInstance = new GenericClass<number>(42);
console.log(numberInstance.getValue()); // 输出: 42

// 使用数组类型实例化类
const arrayInstance = new GenericClass<string[]>(['apple', 'banana', 'cherry']);
console.log(arrayInstance.getValue()); // 输出: ['apple', 'banana', 'cherry']

4.2 定义多个类型参数

定义:

class KeyValueClass<K, V> {
  key: K;
  value: V;

  constructor(key: K, value: V) {
    this.key = key;
    this.value = value;
  }

  getKeyValue(): string {
    return `Key: ${this.key}, Value: ${this.value}`;
  }
}

使用:

// 使用类
const keyValuePair = new KeyValueClass<number, string>(1, 'One');
console.log(keyValuePair.getKeyValue()); // 输出: Key: 1, Value: One

5、泛型约束

5.1 定义约束条件

定义一个接口来描述约束条件:

//  创建一个包含 .length 属性的接口
interface Lengthwise {
    length: number;
}

// 使用这个接口和extends关键字来实现约束:
function loggingIdentity<T extends Lengthwise>(arg: T): T{
    return arg;
}

现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:

loggingIdentity(3);  // Error, number doesn't have a .length property

我们需要传入符合约束类型的值,必须包含必须的属性:

loggingIdentity({length: 10, value: 3});

5.2 在泛型约束中使用类型参数

你可以声明一个类型参数,且它被另一个类型参数所约束
定义:

function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

使用:

const obj = {
  name: 'Alice',
  age: 25,
  hasPet: false
};

// 正常使用
const name = getProperty(obj, 'name'); // 正确,name 的类型被推断为 string
const age = getProperty(obj, 'age');   // 正确,age 的类型被推断为 number

// 错误使用
const weight = getProperty(obj, 'weight');   // 错误!'weight' 不存在于obj类型上

5.3 在泛型里使用类类型

在 TypeScript 中利用泛型来使用类类型,你可以创建一个函数,其参数或返回值的类型可以是一个类构造器(类的静态部分)或类的实例(类的实例部分)

- 使用构造签名

function createInstance<T>(c: { new (): T }): T {
    return new c();
}

- 使用类类型的泛型函数

例如,假设我们有一个类 Animal 和一个工厂函数,我们想让这个函数返回一个 Animal 类型的实例对象:

class Animal {
    name: string;
    constructor(name: string) { this.name = name; }
    move(meters: number) {
        console.log(`${this.name} moved ${meters}m.`);
    }
}

// createInstance 函数可以接受任何具有单个字符串参数构造函数的类
function createInstance<T>(c: { new (name: string): T }): T {
    return new c('Animal Name');
}

let animal: Animal = createInstance(Animal);
animal.move(5); // 输出: Animal Name moved 5m.

- 泛型类和工厂函数

你也可以在泛型类中使用类类型,如下所示:

class BeeKeeper {
  hasMask: boolean = true;
}

class ZooKeeper {
  nameTag: string = 'Mikle';
}

class Animal {
  numLegs: number = 4;
}

class Bee extends Animal {
  keeper: BeeKeeper = new BeeKeeper();
}

class Lion extends Animal {
  keeper: ZooKeeper = new ZooKeeper();
}

function createInstance<A extends Animal>(c: new () => A): A {
  return new c();
}

// 创建 Lion 实例
createInstance(Lion).keeper.nameTag;  // 类型检查 OK
// 创建 Bee 实例
createInstance(Bee).keeper.hasMask;   // 类型检查 OK

文章来源:https://blog.csdn.net/zhenshu_guo/article/details/135415115
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。