TypeScript 泛型实践 | 青训营

41 阅读1分钟

什么是泛型?

我之前学Java的时候,就接触过泛型。其实就是不显式指定传入参数的类型,这样就可以传入不同类型的参数复用代码。

同样的,在TypeScript中,T作为类型的占位符。

类中使用:这样在使用类时才传入具体的类型,collection中就可以存不同类型的数据。

class ArrayOfAnything<T> {
  constructor(public collection: T[]) {}

  get(index: number): T {
    return this.collection[index];
  }
}

函数中使用:可以不用any实现传入不同类型的参数。

function printAnything<T>(arr: T[]): void {
  for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
  }
}

泛型约束

通过继承接口来约束泛型内要实现某些属性或方法

function printHousesOrCars<T extends Printable>(arr: T[]): void {
  for (let i = 0; i < arr.length; i++) {
    arr[i].print();
  }
}

泛型实践

泛型一般是在需要处理多种类型时设置。相同的处理逻辑可以直接在类中实现,不同的逻辑可以写为抽象方法,由继承的子类实现。

实战需求:实现一个读取csv文件的类

解决方案

csv中的数据可能是不同的,我们读取处理后想要得到的数据需求可能是变化的。那么对于处理后得到的数据类型,我们定义为泛型。将针对不同类型会有变化的读取处理数据的部分定义为抽象方法。

import fs from 'fs';

export abstract class CsvFileReader<T> {
  data: T[] = [];

  constructor(public filename: string) {}

  abstract mapRow(row: string[]): T;

  read(): void {
    this.data = fs
      .readFileSync(this.filename, {
        encoding: 'utf-8'
      })
      .split('\n')
      .map(
        (row: string): string[] => {
          return row.split(',');
        }
      )
      .map(this.mapRow);
  }
}

这样在使用中只要给出读取数据的类型,和处理数据的抽象方法的实现就可以了。

type MatchData = [Date, string, string, number, number, MatchResult, string];

export class MatchReader extends CsvFileReader<MatchData> {
  mapRow(row: string[]): MatchData {
    return [
      dateStringToDate(row[0]),
      row[1],
      row[2],
      parseInt(row[3]),
      parseInt(row[4]),
      row[5] as MatchResult,
      row[6]
    ];
  }
}

这样之后增加新的需求也可以复用读取数据的代码,只要提供新的数据类型和抽象方法实现。