我们已经知道了 TypeScript 为变量增加了类型声明,例如 string, number, boolean 等。而泛型可以让你为函数,类和接口定义可复用的通用类型。
用简单的例子来介绍会更加容易理解。
例如我们要编写一个函数返回数字数组中随机一个元素:
function getRandomNumberElement(items: number[]): number {
let randomIndex = Math.floor(Math.random() * items.length);
return items[randomIndex];
}
let numbers = [1, 5, 7, 4, 2, 9];
console.log(getRandomNumberElement(numbers));
假设你需要从一个字符串数组中返回随机一个字符串,这次可以这样实现:
function getRandomStringElement(items: string[]): string {
let randomIndex = Math.floor(Math.random() * items.length);
return items[randomIndex];
}
let colors = ['red', 'green', 'blue'];
console.log(getRandomStringElement(colors));
如果还有其他需求例如从一个对象数组中随机返回一个元素,每次都编写一个函数这种方式就不具备扩展性了。
其中一个解决办法是使用 any 类型,如下:
function getRandomAnyElement(items: any[]): any {
let randomIndex = Math.floor(Math.random() * items.length);
return items[randomIndex];
}
let numbers = [1, 5, 7, 4, 2, 9];
let colors = ['red', 'green', 'blue'];
console.log(getRandomAnyElement(numbers));
console.log(getRandomAnyElement(colors));
这样可以正常工作,不过返回值是 any,此时失去了类型检查的作用。例如:
let ele: string;
ele = getRandomAnyElement(numbers);
console.log(typeof ele); // number
虽然 ele 声明的类型是字符串,但是最后赋值的类型却是数字。
在 TypeScript 中,可以用泛型来表示两个值是同一种类型。此时可以在函数签名中声明一个类型参数如下:
function getRandomElement<T>(items: T[]): T {
let randomIndex = Math.floor(Math.random() * items.length);
return items[randomIndex];
}
这个函数使用了类型变量 T,这个 T 可以获取在调用函数时提供的类型。此外,这个函数也用了 T 类型变量作为它的返回值类型。
这个 getRandomElement 函数是泛型函数,因为它支持所有类型数据组成的数组,例如字符串,数字,对象等。
这里的字母 T 是一般的约定,不过你也可以自由选择其他名字。
我们可以这样调用:
let numbers = [1, 5, 7, 4, 2, 9];
let randomEle = getRandomElement<number>(numbers);
console.log(randomEle);
这个例子在调用 getRandomElement() 函数时显式传递了 number 作为 T 的类型。
实际上我们无需这样做,可以让 TypeScript 编译器根据传递的参数类型自动设置,如下:
let numbers = [1, 5, 7, 4, 2, 9];
let randomEle = getRandomElement(numbers);
console.log(randomEle);
此时,getRandomElement() 函数也可以执行类型强校验。例如把返回值赋值给一个字符串变量就会报错,如下所示:
let numbers = [1, 5, 7, 4, 2, 9];
let returnElem: string;
returnElem = getRandomElement(numbers); // compiler error
如下展示了如何编写一个有两个类型变量 U 和 V 的泛型函数:
function merge<U, V>(obj1: U, obj2: V) {
return {
...obj1,
...obj2
};
}
TypeScript 会推断出 merge() 函数的返回值是 U 和 V 的交集:U & V。
let result = merge(
{ name: 'John' },
{ jobTitle: 'Frontend Developer' }
);
console.log(result); // { name: 'John', jobTitle: 'Frontend Developer' }
继续以上面的 merge() 函数为例,假设调用时第二个参数不是一个对象,而是一个数字:
let person = merge(
{ name: 'John' },
25
);
console.log(person);
它会输出 { name: 'Joh' } ,不会报错。
如果要对参数进行约束,要求它们必须是对象类型。此时可以使用 extends 关键字,例如:
function merge<U extends object, V extends object>(obj1: U, obj2: V) {
return {
...obj1,
...obj2
};
}
修改 merge() 函数后再次运行就会报错:
Argument of type '25' is not assignable to parameter of type 'object'.
TypeScript 还允许声明一个类型参数受另一个类型参数约束。
例如,如下的 prop() 函数有两个参数,其中第二个参数必须是第一个参数的属性:
function prop<T, K>(obj: T, key: K) {
return obj[key];
}
此时编译器会报错:
Type 'K' cannot be used to index type 'T'.
要解决这个问题,为 K 增加一个约束确保它是 T 的一个 key:
function prop<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
此时便可以正常工作了:
let str = prop({ name: 'John' }, 'name');
console.log(str); // John
如果第二个参数传一个不存在的 key 则会报错:
let str = prop({ name: 'John' }, 'age');
错误:
Argument of type '"age"' is not assignable to parameter of type '"name"'.
在 TypeScript 中类也可以是泛型。使用 new 实例化一个泛型类时,它的类型参数同样可以推断出来:
class Box<Type> {
contents: Type;
constructor(value: Type) {
this.contents = value;
}
}
const b = new Box("hello!");
在这个例子中 TypeScript 可以推测得知 Type 为字符串类型。
需要注意的是静态属性不能使用类型参数,因为它的类型是固定的,不应该随着参数而更改。
网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。
添加我为好友,拉您入交流群!
请使用微信扫一扫!