TypeScript 泛型

TypeScript 泛型的学习建议:学习泛型,建议分两步:首先掌握泛型的基本概念,然后再掌握泛型约束的使用。

1、泛型是什么?

泛型是一种特殊的变量,只用于表示类型而不是值,称之为:类型变量。泛型通常用尖括号<T>来表示,T 是泛型的常用名称,T 代表Type。实际上 T 可以用任何有效名称代替,除了 T 之外,以下是常见泛型变量代表的意思:

  • K(Key):表示对象中的键类型;
  • V(Value):表示对象中的值类型;
  • E(Element):表示元素类型。

2、为什么使用泛型?

为更好地理解使用泛型的理由,可以看下面的 getArray 函数,它可以生成 any 类型的项的数组:

function getArray(items : any[]) : any[] {
    return new Array().concat(items);
}

我们可以调用 getArray 函数并向其传递一个数字数组来声明 numberArray 变量,也可以使用一个字符串数组来声明 stringArray 变量。 但是,由于使用了 any 类型,因此没有什么可以阻止代码将 string 赋值到 numberArray 或将 number 赋值到 stringArray。

let numberArray = getArray([5, 10, 15, 20]);
let stringArray = getArray(['Cats', 'Dogs', 'Birds']);
numberArray.push(25);                       // OK
stringArray.push('Rabbits');                // OK
numberArray.push('This is not a number');   // OK
stringArray.push(30);                       // OK
console.log(numberArray);                   // [5, 10, 15, 20, 25, "This is not a number"]
console.log(stringArray);                   // ["Cats", "Dogs", "Birds", "Rabbits", 30]

如果想要在调用函数时确定数组将包含的值的类型,然后让 TypeScript 对传递给它的值进行类型检查以确保它们属于该类型,该怎么操作? 这时泛型就可以发挥作用了。下面的例子使用泛型重写 getArray 函数。现在,它可以接受你在调用函数时指定的任何类型。

function getArray<T>(items : T[]) : T[] {
    return new Array<T>().concat(items);
}

泛型定义一个或多个“类型变量”来标识要传递给组件的一个或多个类型,用尖括号 (< >) 括起来。在上面的示例中,函数中的类型变量称为 <T>。

指定类型变量后,可以使用它来代替参数中的类型、返回类型或将其置于函数中要添加类型批注的任何其他位置。类型变量 T 可用于任何需要类型批注的位置。 在 getArray 函数中,它用于指定 items 参数的类型、函数返回类型和返回新的项数组。

若要调用函数并向其传递类型,请将 <type> 追加到函数名称。例如,getArray<number> 指示函数仅接受 number 值的数组,并返回 number 值的数组。 因为类型已指定为 number,所以 TypeScript 会预期将 number 值传递给函数,如果传递的是其他值,则会引发错误。

在下面例子中,通过更新 numberArray 和 stringArray 的变量声明以调用具有所需类型的函数,TypeScript 可阻止将无效项添加到数组中。

let numberArray = getArray<number>([5, 10, 15, 20]);
numberArray.push(25);                      // OK
numberArray.push('This is not a number');  // Generates a compile time type check error

let stringArray = getArray<string>(['Cats', 'Dogs', 'Birds']);
stringArray.push('Rabbits');               // OK
stringArray.push(30);                      // Generates a compile time type check error

3、什么是泛型约束?为什么使用泛型约束?如何使用泛型约束?

泛型约束即是对泛型的类型进行约束控制,当在函数里使用泛型参数的属性或者方法时,就需要对泛型进行约束。如下所示的 identity 函数可以接受你选择要传递到类型变量的任何类型。但是,在这种情况下,你应该将 value 参数可以接受的类型限制为可以对其执行添加操作的一系列类型,而不是接受任何可能的类型。这种情况称为“泛型约束”

为了实现泛型约束,我们自定义 type 类型,然后使用自定义的 type 类型 extend 类型变量 <T>。下面的示例将 ValidTypes 声明为带有 string 和 number 的元组。 然后,用它来扩展类型变量 <T>。 现在,只能将 number 或 string 类型传递给类型变量。

type ValidTypes = string | number;

function identity<T extends ValidTypes, U> (value: T, message: U) : T {
    let result: T = value + value;    // Error
    console.log(message);
    return result
}

let returnNumber = identity<number, string>(100, 'Hello!');      // OK
let returnString = identity<string, string>('100', 'Hola!');     // OK
let returnBoolean = identity<boolean, string>(true, 'Bonjour!'); // Error: Type 'boolean' does not satisfy the constraint 'ValidTypes'.

另外,在泛型约束中,我们还可以将类型限制为另一个对象的属性。下面的例子将 extendskeyof 运算符一起使用,该运算符采用对象类型并生成其键的字符串或数字文本并集。此处,K extends keyof T 确保键参数的类型对于分配给 pet 的类型来说是正确的。

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

let pets1 = { cats: 4, dogs: 3, parrots: 1, fish: 6 };
let pets2 = { 1: "cats", 2: "dogs", 3: "parrots", 4: "fish"}

console.log(getPets(pets1, "fish"));  // Returns 6
console.log(getPets(pets2, "3"));     // Error