一、泛型函数

TypeScript泛型是一种可以使代码具有更高的可重用性和泛化能力的特性。通过泛型,我们可以定义一种通用的类型或函数,使其能够应对多种类型的输入。泛型在类、函数、接口等多种场景下都可以使用。

具体来说,在定义泛型函数时,我们可以使用来表示一个类型变量,这样我们就可以在函数中使用这个泛型类型来作为参数类型、返回值类型或变量类型等。例如:

function echo<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = echo;

在这个例子中,我们定义了一个echo函数,它使用泛型类型变量T作为输入参数类型和返回值类型,这样我们就可以使用不同类型的参数来调用该函数,例如:

console.log(echo('Hello TypeScript!')); // 输出:Hello TypeScript!
console.log(echo(123)); // 输出:123

二、泛型类

在类的定义中使用泛型,也可以大大提高代码的复用性和灵活性。例如下面的代码:

class Stack<T> {
    private items: T[] = [];

    push(item: T) {
        this.items.push(item);
    }

    pop(): T {
        return this.items.pop();
    }
}

let strStack = new Stack<string>();
strStack.push('apple');
strStack.push('banana');
strStack.push('cherry');

console.log(strStack.pop());
console.log(strStack.pop());
console.log(strStack.pop());

在上面的代码中,定义了一个Stack类,并声明了泛型类型T,用于在push和pop方法中进行类型的转换。通过传入不同类型的参数,可以得到不同类型的Stack实例。

三、泛型接口

interface Map<K, V> {
    set(key: K, val: V): void;
    get(key: K): V | undefined;
    has(key: K): boolean;
    clear(): void;
}

let numMap: Map<number, string> = {
    set(key: number, val: string) {
        // 添加键值对
    },
    get(key: number): string | undefined {
        // 获取键值对
    },
    has(key: number): boolean {
        // 判断是否存在
    },
    clear() {
        // 清空Map
    }
};

numMap.set(1, 'apple');
numMap.set(2, 'banana');

console.log(numMap.get(1));
console.log(numMap.get(2));

在上面的代码中,定义了一个Map接口,并使用泛型K和V来约束键和值的类型。通过传入不同类型的参数,可以得到不同类型的Map实例,并实现对不同类型键值对的操作。

四、泛型约束、泛型限制用法<T> extends {属性:类型}

在 TypeScript 中,泛型约束是一种让泛型类型参数只能取某些特定类型的值的机制。这个特定类型的限制可以是一个接口、类、枚举或其他类型。

例如,假设我们有一个泛型函数,它接受一个类型参数 T 和一个对象参数 obj,这个函数返回 obj 中的第一个 T 类型的属性值:

function getPropertyValue<T>(obj: object): T {
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      let value = obj[key];
      if (value instanceof T) {
        return value;
      }
    }
  }
  throw new Error(`No property of type ${T.name} found.`);
}

但是,这个函数可能会遇到一些问题。例如,如果我们调用 getPropertyValue({ a: ‘1’, b: 2, c: 3 }),那么会抛出一个错误,因为字符串 ‘1’ 不是一个 number 类型。

为了解决这个问题,我们可以使用泛型约束来限制 T 只能取某些特定类型的值。例如,我们可以要求 T 必须是实现了 Number 接口的类型:

interface Number {
  new (value?: any): number;
  (value?: any): number;
  readonly NaN: number;
  readonly MAX_VALUE: number;
  readonly MIN_VALUE: number;
  readonly NEGATIVE_INFINITY: number;
  readonly POSITIVE_INFINITY: number;
}

function getPropertyValue<T extends Number>(obj: object): T {
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      let value = obj[key];
      if (value instanceof T) {
        return value;
      }
    }
  }
  throw new Error(`No property of type ${T.name} found.`);
}

现在,如果我们调用 getPropertyValue({ a: ‘1’, b: 2, c: 3 }),就会得到一个类型错误,因为 ‘1’ 不是一个实现了 Number 接口的类型。但是,如果我们调用 getPropertyValue({ a: 1, b: 2, c: 3 }),就能得到正确的结果了。

另一泛型约束\<key extends keyof Type>

在 TypeScript 中,<key extends keyof Type> 是一种泛型约束方式,用于限制一个泛型类型参数 key 的范围。

其中,keyof 关键字可以用于获取一个类型 Type 的所有属性名,返回一个字符串字面量类型,例如:

interface Person {
  name: string;
  age: number;
}

type PersonKeys = keyof Person; // "name" | "age"

<key extends keyof Type> 中,extends 关键字表示限制 key 的取值只能是 Type 类型中已有的属性名。也就是说,只有 key 取值为 Type 类型中已有的属性名才符合类型约束,例如:

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

const person = {
  name: 'Bob',
  age: 25,
};
const name = getValueByKey(person, 'name'); // name 的类型是 string
const age = getValueByKey(person, 'age'); // age 的类型是 number
const gender = getValueByKey(person, 'gender'); // 编译报错,gender 不是 person 中的属性

在上面的例子中,getValueByKey 函数接收两个参数:一个泛型类型参数 T,代表输入对象的类型;一个泛型类型参数 K,代表属性名的类型。

extends keyof T 约束了类型参数 K 必须是输入对象类型 T 中已存在的属性名。因此,调用 getValueByKey 函数传入一个不存在的属性名 gender 会引发编译错误。

五、使用泛型注意点

在编写泛型方法时,应该注意以下几点:

  1. 泛型形参的名称应该描述清楚其作用和范围,尽量不要使用单个字母的形参名称;
  2. 在使用泛型类型的属性或方法时,应该确保该属性或方法在所有可能的泛型类型中都是存在的,不要使用不存在的属性或方法;
  3. 在使用泛型类型的操作符时,应该考虑不同类型之间可能的差异性和兼容性问题,避免出现类型错误

更多推荐