这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。
一.概念
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。这句定义可以简单的拆分理解,定义不校验类型,使用时进行类型校验。定义不明确,使用明确。
泛型和any的区别:编译期间 any 无法进行类型安全检查,而泛型在编译期间可以进行类型安全检查。any 类型可以获取任意数据类型的任何属性和任意方法而不会出现编译错误导致潜在错误风险,而泛型却有效的避免了此类问题发生,注意object是具有点击未知属性进行错误提示。any 类型数据获取属性和方法时无自动提示,泛型有自动提示
二.使用动机
需求1:我们需要使用Ts书写一个函数创建一个数组,数组的数据类型是由传入的数据类型决定的,如何做?
解法1:
function createArray(value: any, num: number): Array<any> {
let arr = [];
for (let i = 0; i < num; i++) {
arr[i] = value;
}
return arr;
}
方式1采用any解决类型限制,缺点很明显无法准确知道返回类型的值是什么,传入的值为string,我想用返回值提供的默认length属性,但是any无法做到
解法2:
function createArray<T>(value: T, num: number): Array<T> {
let arr:Array<T> = [];
for (let i = 0; i < num; i++) {
arr[i] = value;
}
return arr;
}
这种解法完美的解决了刚刚输入输出类型确定,加深了概念定义不明确,使用明确。
三.使用姿势
有时候很迷惑的地方就是不知道何时应该使用泛型,比如以下例子是否有用泛型的必要
function foo<T>(arg: T): void;
function foo<T>(arg: any): T;
以上两种模式都是误用泛型了,可以发现输入输出包括成员之间没有起到约束的效果,属于误用泛型
四.泛型约束
需求2:紧接需求1创建一个函数用于返回数组,但是这个函数输入的数据类型必须会具有length的属性,这个时候泛型如何来约束传入的数据类型。
interface LengthType {
length: number;
}
function createArray<T extends LengthType>(value: T, num: number): Array<T> {
let arr: Array<T> = [];
for (let i = 0; i < num; i++) {
arr[i] = value;
arr[i].length;
}
return arr;
}
可以看到上述代码中,我们使用了extends关键字,给泛型T加一个约束,传入的数据类型要进行泛型约束,必须具有length属性
需求3:有两个对象A,B,我要B中所有key值都赋值给A中同等key,如何使用?
function handleKey<A extends B, B>(obja: A, objb: B): A {
for (let id in objb) {
obja[id] = (<A>objb)[id];
}
return obja;
}
解析:使用继承的方式很简单的将输入类型进行了约束,限制成员之间的关系,上述中A继承于B,保证了B上的属性A一定有,保证了赋值安全。
例子:
type RequestFunction<P = Record<string, any> | void, R = any> = ( params: P, ...args: any[] ) => Promise<R>;
这个泛型如何解释呢,一是看Record是定义一个对象的具体key,value类型,Record<key,value>。然后二是看定义了两个泛型。然后三是那可以理解P泛型的默认值是key为string,value为any的对象类型或者为空,泛型R默认值为any。然后四是参数类型是P和array,返回值是一个R类型的Promise。