泛型的由来
如果想要创建可处理多种类型而不只是一种类型的组件,该怎么操作? 可以使用 any 类型,但这样就失去了 TypeScript 类型检查系统的功能。
泛型的特点
-
(可复用行性) 泛型是可以在代码库中定义和重复使用的代码模板。
-
(定义时指示性) 它们提供了一种方法,可用于指示函数、类或在调用接口时要使用的类型。
-
(调用时指示性) 可以通过将参数传递给函数的方式来理解,不同之处是使用泛型可以指示组件在被调用时应该使用哪种类型。
使用时机
当代码是满足以下条件的函数或类时,创建泛型函数:
-
处理各种数据类型。 在多个位置使用该数据类型。 泛型可以:
-
在处理类型时提供更大的灵活性。
-
实现代码重用。
-
减少使用 any 类型的需要。
示例 :getArray 函数生成 any 类型的项的数组
// 使用any 传入任何参数 并返回任何参数
function getArray(items: any[]): any[] {
return new Array().concat(items);
}
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]
上面是 any 类型 没有类型检查等 ,如果想要在调用函数时确定数组将包含的值的类型,然后让 TypeScript 对传递给它的值进行类型检查以确保它们属于该类型,该怎么操作? 这时泛型就可以发挥作用了。
泛型函数定义
使用泛型重写 getArray 函数。 现在,它可以接受你在调用函数时指定的任何类型。
function getArray<T>(items: T[]): T[] {
return new Array<T>().concat(items);
}
-
泛型定义一个或多个“类型变量”来标识要传递给组件的一个或多个类型(用尖括号 (< >) 括起来)
-
函数中的类型变量称为 <T>。 T 是泛型的常用名称,但可以根据需要对其进行命名。
-
指定类型变量后,可以使用它来代替参数类型、返回类型或将其置于函数中要添加类型批注的任何其他位置。
调用泛型函数
若要调用函数并向其传递类型,请将 <type> 追加到函数名称。
- 例如,getArray<number> 表示了
-
指示函数仅接受 number 值的数组
-
并返回 number 值的数组。
-
因为类型已指定为 number,所以 TypeScript 会预期将 number 值传递给函数,如果传递的是其他值,则会引发错误。
-
如果在调用函数时省略类型变量,TypeScript 将推断类型。 但是,这仅适用于简单数据。
如果传入数组或对象,会导致推断任何类型(any)并消除类型检查。
function getArray<T>(items: T[]): T[] {
return new Array<T>().concat(items);
}
let numberArray = getArray<number>([5, 10, 15, 20]);
numberArray.push(25); // OK
numberArray.push('This is not a number'); // 类型“string”的参数不能赋给类型“number”的参数。
let stringArray = getArray<string>(['Cats', 'Dogs', 'Birds']);
stringArray.push('Rabbits'); // OK
stringArray.push(30); // 类型“number”的参数不能赋给类型“string”的参数。
使用多个类型变量
泛型组件中并不是只能使用单个类型变量。
// 使用多个变量
function person11<T, U>(name: T, age: U): U {
console.log(age);
return age
}
let returnNumber = person11<string, number>('王大可1', 18);
let returnString = person11<string, string>('王大可2', '28');
let returnBoolean = person11<string, boolean>('王大可3', true);
returnNumber = returnNumber * 100; // OK
returnString = returnString * 100; // 错误 算术运算左侧必须是 "any"、"number"、"bigint" 或枚举类型
returnBoolean = returnBoolean * 100; // 错误 算术运算左侧必须是 "any"、"number"、"bigint" 或枚举类型
使用泛型类型的方法和属性
上面的T,U 非常宽泛,导致无法再函数内部无法使用变量。
使用类型变量创建泛型组件时,只能使用每种类型可用的对象的属性和方法。
防止参数不兼容的操作引起错误。
function person11<T, U>(name: T, age: U): U {
let result: T = name + name; // Error 运算符“+”不能应用于类型“T”和“T”
console.log(age);
return age
}
使用泛型约束来限制类型
为了解决上面的问题故而限制类型变量的变量范围
-
自定义 type 声明为元组 然后使用自定义类型 extend 类型变量
-
类型限制为另一个对象的属性
//自定义 type 声明为元组
type LimitTypes = string | number;
function person11<T extends LimitTypes, U>(name: T, age: U): U {
let result: T = name + name; // Error 运算符“+”不能应用于类型“T”和“T”
console.log(age);
return age
}
// 类型限制为另一个对象的属性
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")); // 输出 6
console.log(getPets(pets2, "3")); // Error 类型“"3"”的参数不能赋给类型“4 | 3 | 1 | 2”的参数。
对泛型使用类型保护
在上面的例子中 虽然限定了泛型类型 LimitTypes 但是还是不能执行操作,这时可以使用类型保护来处理逻辑。
function person11<T, U extends LimitTypes>(name: T, age: U) {
// let result: T = name + name; // Error 运算符“+”不能应用于类型“T”和“T”
let result: LimitTypes = '';
let typeValue: string = typeof age;
if (typeof age === 'number') { // Is it a number?
result = age + age; // OK
} else if (typeof age === 'string') { // Is it a string?
result = age + age; // OK
}
console.log(`年龄是 ${age} 类型是 ${typeValue} 返回是 ${result}`);
console.log(age);
return result
}
let returnNumber = person11<string, number>('王大可1', 18);
let returnString = person11<string, string>('王大可2', '28');
只能使用 typeof 类型保护来检查基元类型 string、number、bigint、function、boolean、symbol、object 和未定义类型。
若要检查类的类型,请使用 instanceof 类型保护。