泛型

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2022-06-17

泛型编程允许程序员使用以后才会定义的类型,并在实例化时作为参数指定这些类型。

在函数名后面增加一个对角括号代表是一个泛型函数,如果角括号内字符T,表示是一个类型

// 普通函数
function common(arg: number): number {
  return arg;
}

// 泛型函数的类型与普通函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样
function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

// 1. 也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。
let myIdentity: <U>(arg: U) => U = identity;

// 2. 还可以使用带有调用签名的对象字面量来定义泛型函数
let myIdentity: { <T>(arg: T): T } = identity;

我们把上面例子里的对象字面量拿出来做为一个接口也就是泛型接口

interface GenericIdentityFn {
  <T>(arg: T): T;
}

let myIdentity: GenericIdentityFn = identity;

当一个函数A返回一个泛型函数B时,这个泛型函数B的类型参数就是函数A的返回值类型作为其类型参数的实参

function fn2<T>(n: number): T {
  return n as unknown as T;
}

function fn1(n: number): Promise<number> {
  return fn2(n);
}

// 此例中是函数fn1返回一个泛型函数fn2,fn1的返回类型 Promise<number> 就会赋值给fn2的泛型T做类型参数
// 相当于
function fn1(n: number): Promise<number> {
  return fn2<Promise<number>>(n);
}

泛型也是一种类型,只不过不同于 string, number 等具体的类型,它是一种抽象的类型,只有在调用的时候才确定它的值。

泛型可以理解为 处理类型 的函数,其中的T,相当于是函数里面的形参,在泛型中可以称为类型形参,调用的是时候传入的类型是类型实参,同样的,跟函数一样,在泛型的body中可以使用这个形参。

泛型类

指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。

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; };

如何使用泛型类

当需要封装一个 ajax 请求,返回响应数据,针对不同的业务,返回的响应数据必然不同,这时候,在定义这个 ajax 请求类的时候就可以使用泛型类;

class HandleAjax<T> {
  private _url: string;
  constructor(url: string) {
    this._url = url;
  }

  public getAsync() {
    return Q.Promise((resolve: (entities: T[]) => void, reject) => {
      $.ajax({
        url: this._url,
        type: 'GET',
        dataType: 'json',
        success: (data) => {
          var list = data.items as T[];
          resolve(list);
        },
        error: (e) => {
          reject(e);
        },
      })
    })
  }
}

// 定义获取 用户数据的 HandleAjax实例,传入泛型 User
var usrAjax = new HandleAjax<User>('./demos/user.json');
usrAjax.getAsync().then((users: User[]) => {
  console.log('users', users);
})

// 定义获取 会议数据的 HandleAjax实例,传入泛型 Talk
var talkAjax = new HandleAjax<Talk>('./demos/talk.json');
talkAjax.getAsync().then((talks: Talk[]) => {
  console.log('talks', talks);
})

泛型约束

使用泛型约束时,T 就不再是任意类型,而是被实现接口的 shape,当然你也可以继承多个接口

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);  
  return arg;
}

// 定义了约束,因此它不再是适用于任意类型:
loggingIdentity(3);  // Error, number doesn't have a .length property

在泛型约束中使用类型参数

// 接受两个参数 obj key  key必须是obj的属性
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m"); // error

如何使用泛型约束

有时候,我们需要约束泛型类,例如上例中的ajax请求,我们需求增加验证,只有当请求到数据并且验证有效后返回;

通常我们会这么做,

class HandleAjax<T>{
  // ...
  success: (data) => {
    var items = data.items as T[];
    for (var i = 0; i < items.length; i++) {
      if (items[i] instanceof User) {
        // validate user
      }

      if (items[i] instanceof Talk) {
        // validate talk
      }
    }
  }
  // ...
}

这样做的问题,就是每增加一个新的有效实例,都必须修改 HandleAjax 类增加额外逻辑。事实上,我们不应该将验证逻辑加入泛型类,因为一个泛型类不应该知道泛型的类型;

一个更好的解决方案是给要获取的实例增加一个 isValid 方法,它在实例验证通过后返回 true, 但是只针对HandleAjax来说,它是一个泛型类,将能与任意类型的实体一起使用,但不是所有的的类型都有 isValid 方法,这时候就需要使用泛型约束。

// 约束满足 ValidatableInterface 接口的必须拥有 isValid 方法
interface ValidatableInterface {
  isValid(): boolean;
}

class User implements ValidatableInterface { // 这样,就能保证 User类必须有 isValid 方法
  public name: string;
  public password: string;
  public isValid(): boolean {
    // user validation...
    return true;
  }
}

// 现在为 泛型类 HandleAjax 加上泛型约束
class HandleAjax<T extends ValidatableInterface> {
  private _url: string;
  constructor(url: string) {
    this._url = url;
  }

  public getAsync() {
    return Q.Promise((resolve: (entities: T[]) => void, reject) => {
      $.ajax({
        url: this._url,
        type: 'GET',
        dataType: 'json',
        success: (data) => {
          var list: T[];
          var items = data.items as T[];
          for (var i = 0; i < items.length; i++) {
            if (items[i].isValid()) {
              list.push(items[i]);
            }
          }
          resolve(list);
        },
        error: (e) => {
          reject(e);
        },
      })
    })
  }
}

在泛型约束中使用多重类型

interface IMyInterface {
  doSomething();
}

interface IMySecondInterface {
  doSomethingElse();
}

// 错误的写法 不能在定义泛型约束的时候指定多个类型
class Example <T extends IMyInterface, IMySecondInterface> {
  private data: T;
  useT() {
    this.data.doSomething();
    this.data.doSomethingElse(); // error 实际上第二个类没有被继承
  }
}

// 正确的写法 这样,原来的接口都变成了超接口
interface IMyAllInterface extends IMyInterface, IMySecondInterface {}

class Example <T extends IMyAllInterface> {
  private data: T;
  useT() {
    this.data.doSomething();
    this.data.doSomethingElse();
  }
}

泛型中的 new 操作

function factory<T>(c: { new(...args: any): T }): T {
  console.log(c); // [Function: Person]
  return new c();
}

class Person {

  constructor() {
    console.log('Person 被实例化了')
  }
}

var myPerson = factory<Person>(Person);

console.log(myPerson) // Person {}

泛型的默认参数

type A<T = string> = Array<T>;
const aa: A = [1]; // type 'number' is not assignable to type 'string'.
const bb: A = ["1"]; // ok
const cc: A<number> = [1]; // ok
// 如果不指定的话,会默认为 string 类型

export interface IResponse<T = any> {
  code: number;
  msg: string;
  data?: T;
}

interface Result {
  result: boolean;
  data: IResponse; // 可以不传
}

interface Result2 {
  result: boolean;
  data: IResponse<string>; // 可以不传
}