Generics 泛型
假如:需要一个函数,返回传入它的内容。不使用泛型时,它会是这样的:
// 案例:
// 这个函数接受两个参数。第一个参数为任意类型的值,第二个参数为数值类型的值,默认为 5。
const getArray = (value: any, times: number = 5): any[] => {
return new Array(times).fill(value);
};
getArray([1], 2).forEach(item => {
console.log(item.length);
});
getArray(2, 3).forEach(item => {
console.log(item.length); // 报错
});
typescript
我们调用了两次这个方法,使用 forEach 方法遍历得到的数组,在传入 forEach 的函数中获取当前遍历到的数组元素的 length 属性。
上面例子中第二次调用getArray
的返回值每个元素应该是数值类型,遍历这个数组时我们获取数值类型的length属性也没报错,因为这里item的类型是any。
上面的示例,定时any时,丢失了一些信息:
- 传入的类型与返回的类型应该是相同的;
- 如果我们传入一个数字,我们只知道任何类型的值都有可能被返回;
于是此时需要一种方法使返回值的类型与传入参数的类型是相同的。
定义
泛型指的是,在定义函数、接口或类的时候不预先指定数据类型,而在使用时再指定类型的特性。
作用:泛型可以提升应用的可重用性,如使用其创建组件,则可以使组件可以支持多种数据类型。
拿上面这个例子中的逻辑来举例:
const getArray = <T>(value: T, times: number = 5): T[] => {
return new Array(times).fill(value);
};
typescript
这个 T 在这次函数定义中就代表某一种类型,它可以是基础类型,也可以是联合类型等高级类型。
现在我们再来调用一下这个 getArray 函数:
getArray<number[]>([1, 2], 3).forEach(item => {
console.log(item.length);
});
getArray<number>(2, 3).forEach(item => {
console.log(item.length); // 类型“number”上不存在属性“length”
});
typescript
在调用 getArray 的时候,在方法名后面使用<>
传入了我们的泛型变量 T 的类型number[]
,那么在定义 getArray 函数时使用 T 指定类型的地方,都会使用number[]
指定。
泛型变量
泛型变量,这是一种特殊的变量,只用于表示类型而不是值。
但是,你也可以省略这个<number[]>
,TypeScript 会根据你传入函数的 value 值的类型进行推断:
getArray(2, 3).forEach(item => {
console.log(item.length); // 类型“number”上不存在属性“length”
});
typescript
由于我们在定义getArray时,给value也定义了T类型,那么,传入实参时,会同时将实参的类型传递给类型变量 T。
应用场景:交换两个数组元素
不使用泛型会丢失数据类型:
function swap(tuple) {
return [tuple[1], tuple[0]]
}
typescript
使用泛型后,不仅会保有类型推断,还可以直接调用实例的方法:
function swapGeneric<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
const result2 = swapGeneric(['string', 0.123])
// ts的类型推断系统能够明确得知第一个元素会是数值,而第二个元素会是字符串
result2[0].toFixed(2)
result2[1].toLocaleUpperCase()
typescript
泛型约束
主要有两种情况:
- 属性约束,就是使用一个类型和
extends
对泛型进行约束。
可以使用接口定义一个对象必须有哪些属性:interface ValueWithLength { length: number; } const v: ValueWithLength = {}; // error Property 'length' is missing in type '{}' but required in type 'ValueWithLength'
typescript
泛型约束就是使用一个类型和extends
对泛型进行约束,之前的例子就可以改为下面这样:interface ValueWithLength { length: number; } const getLength = <T extends ValueWithLength>(param: T): number => { return param.length; }; getLength("abc"); // 3 getLength([1, 2, 3]); // 3 getLength({ length: 3 }); // 3 getLength(123); // error 类型“123”的参数不能赋给类型“ValueWithLength”的参数
typescript - 类型参数约束,当我们定义一个对象,想要对只能访问对象上存在的属性做要求时,该怎么办?
先来看下这个需求是什么样子:const getProps = (object, propName) => { return object[propName]; }; const obj = { a: "aa", b: "bb" }; getProps(obj, "c"); // undefined
typescript
当我们访问这个对象的c
属性时,这个属性是没有的。需要用到索引类型keyof
结合泛型来实现对这个问题的检查:// 使用让K来继承索引类型keyof T const getProp = <T, K extends keyof T>(object: T, propName: K) => { return object[propName]; }; const obj = { a: "aa", b: "bb" }; // 在这里 K 就被约束为了只能是"a"或"b" getProp(obj, "c"); // 类型“"c"”的参数不能赋给类型“"a" | "b"”的参数
typescript
你可以理解为keyof
的T相当于一个由泛型变量T的属性名构成的联合类型。
泛型扩展
泛型函数
// ex1: 简单定义
const getArray: <T>(arg: T, times: number) => T[] = (arg, times) => {
return new Array(times).fill(arg);
};
// ex2: 使用类型别名
// 类型别名就是给一种类型起个别的名字,之后只要使用这个类型的地方,都可以用这个名字作为类型代替
type GetArray = <T>(arg: T, times: number) => T[];
const getArray: GetArray = <T>(arg: T, times: number): T[] => {
return new Array(times).fill(arg);
};
// 也可以使用接口的形式来定义泛型函数类型:
interface GetArray {
<T>(arg: T, times: number): T[];
}
const getArray: GetArray = <T>(arg: T, times: number): T[] => {
return new Array(times).fill(arg);
};
typescript
泛型类
场景:定义一个类,能实现被 push 入的队列元素与 pop 出的元素的类型一致。
class Queue<T> {
private data = []
push(item: T) {
return this.data.push(item)
}
pop(): T {
return this.data.pop()
}
}
//泛型类实例化时要指定具体的类型
const queue = new Queue<number>()
queue.push(1)
queue.push('str') // Error: 类型“string”的参数不能赋给类型“number”的参数。
typescript
泛型接口
万能的泛型同样可以用来描述接口:
interface KeyPair<T, U> {
key: T
value: U
}
// 泛型接口描述的对象,同样需要满足类型要求
let kp1: KeyPair<number, string> = { key: 123, value: 'str' }
let kp2: KeyPair<string, number> = { key: 'test', value: 123 }
// 描述函数的泛型接口
interface IPlus<T> {
// 函数应具有两个形参,和一个返回值,它们的类型相同
(a: T, b: T): T
}
function plus(a: number, b: number): number {
return a + b
}
function concat(a: string, b: string): string {
return a + b
}
typescript
参数类型不兼容:
↑