简介
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
function createArray<T>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;}// 指定T为stringcreateArray<string>(3, 'x'); // ['x', 'x', 'x']// ⭐当然也可以不指定,让类型推断自动推算:createArray(3, 'x'); // 推断出T为string类型
多个类型参数
定义泛型的时候,可以一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {return [tuple[1], tuple[0]];}swap([7, 'seven']); // ['seven', 7]
泛型约束extends
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法,这时候可以用extends来进行泛型约束:
interface Lengthwise {length: number;}// extends 约束了传参泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性。function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length);return arg;}loggingIdentity(7); // 报错:类型“number”的参数不能赋给类型“Lengthwise”的参数。
多个类型参数之间也可以互相约束:
// T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。function copyFields<T extends U, U>(target: T, source: U): T {for (let id in source) {target[id] = (<T>source)[id];}return target;}let x = { a: 1, b: 2, c: 3, d: 4 };copyFields(x, { b: 10, d: 20 });
泛型约束extends还可以用于三元表达式,很多泛型工具函数都是利用了联合类型在extends时条件分发功能,如Exclude
interface Fish {type: '鱼'}interface Bird {type: '鸟'}interface Swim {swim(): void}interface Fly {fly(): void}type MyType<T> = T extends Fish ? Swim : Flytype MyType2 = MyType<Bird> // Fly// ⭐如果传入的是联合类型,会进行条件的分发,有一个符合就返回一个type MyType3 = MyType<Bird | Fish> // Swim | Fly// 交叉类型&不具备分发type MyType3 = MyType<Bird & Fish> // Swim 其实Bird & Fish交叉类型为never,never可以赋给任意类型,所以可以赋给Fish,所以返回Swimtype Exclude<T, k> = T extends k ? never : T // 从联合类型T中剔除掉某个k的类型,不是取差集type Extract<T, k> = T extends k ? T : never // 从T中取出k的类型,取交集type NonNullable<T> = T extends null | undefined ? never : T // 去除null和undefined
泛型推断infer
infer必须在extends条件语句中才可以使用,表示待推断的类型变量。
// ⭐获取函数返回值类型// 前面的extends是限制传入的参数T必须是函数类型,后面的extends是为了使用infer(infer必须在extends语句中使用)type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any; // 这里infer R放在返回值后面表示推断返回值类型// ⭐获取函数参数类型// 如果infer R放在参数后面,就代表推断参数类型,也就是Parameters的实现:type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;// 例子:function getType(x: number, y: string) {return { name: 'xjp', age: 12 }}type getReturnType = ReturnType<typeof getType> // { name: string; age: number; }type getParameters = Parameters<typeof getType> // [x: number, y: string]// ⭐获取构造函数的参数类型type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;// ⭐获取实例类型(实例就是new Class,而new Class就是执行类的构造函数constructor,所以获取实例类型就是获取类构造函数的返回值类型)type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;// 例子class Person {constructor(name: string) { }}type getConstructorParameters = ConstructorParameters<typeof Person> // [name: string]type getInstanceType = InstanceType<typeof Person> // Person。所以类作为类型实际上就是实例的类型,而typeof class则是完整的类的类型(包括prototype等)
泛型接口
可以使用接口的方式来定义一个函数需要符合的形状,当然也可以使用含有泛型的接口来定义函数的形状:
interface CreateArrayFunc {<T>(length: number, value: T): Array<T>;}let createArray: CreateArrayFunc = function <T>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;};// 进一步,把泛型参数提前到接口名上:interface CreateArrayFunc<T> {(length: number, value: T): Array<T>;}let createArray: CreateArrayFunc<any> = function <T>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;};createArray(3, "x"); // ['x', 'x', 'x']
泛型类
与泛型接口类似,泛型也可以用于类的类型定义中:
class GenericNumber<T> {zeroValue: T;add: (x: T, y: T) => T;}let myGenericNumber = new GenericNumber<number>();myGenericNumber.zeroValue = 0;myGenericNumber.add = function(x, y) { return x + y; };
泛型参数的默认类型
当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用:
function createArray<T = string>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;}
误用泛型
1、泛型仅作用在单个参数
declare function foo<T>(arg: T): void;// 上面的泛型完全没有必要使用,因为它仅用于单个参数的位置,使用如下方式可能更好:declare function foo(arg: any): void;
2、仅返回使用
declare function parse<T>(name: string): T;// 上面泛型只在一个地方被使用了,它并没有在成员之间提供约束 T
