typescript进阶(三):泛型

232 阅读4分钟

泛型是什么?

百度百科:泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。

通俗易懂:泛型就用动态类型(类型变量)解决类、接口、函数的复用性的方式。

泛型的重要性?

js作为一门弱类型计算机语言,在前端飞速发展的现阶段,弱类型语言的缺点也是暴露无遗,高度自由化的同时带来的是代码质量的参差不齐。 typescript是基于js的一门强类型的高级语言,强类型语言中变量的赋值或引用是很严格的,泛型就是强类型语言自我约束自由化的特征。对于前端小伙伴们来说,刚接触泛型是很不好理解的,这都是正常的现象,毕竟惯性于弱类型语言的高度自由化,切换到强类型语言确实是需要一个过程。能不能学好typescript就得从泛型开始

开始

我们来实现几个基础的泛型使用,最后我们引入一个实践例子来实战。

泛型方法

//参数为string,返回值为string
function inputFn(input: string): string {
  return input;
}

//参数为number,返回值为number
function inputFn(input: number): number {
  return input;
}

//参数为联合类型string | number, 返回值typescript会自推断
function inputFn(input: string | number) {
  return input;
}

上面例子是一些简单的类型,如果现在我们需要传入一个对象{name: '张三'},联合类型string | number | {naem: string}就得不停的往后加,这明显不是一个好的实现方式,泛型就能很好的解决这个问题。

//T就是我们需要传入的类型,此时参数就是我们的T,所以现在可以传入任何类型
function inputFn<T>(input: T): T {
  return input;
}
inputFn(123) //123
inputFn('123') //'123'
inputFn({name: '张三'}) //{name: '张三'}

大家看到这是不是觉得泛型的自由度太高了,这时候我们还可以可以通过传入类型变量来约束泛型,泛型主要是为了约束类型范围, 如果不能约束类型范围太自由化, 就代表当前场景不适合使用用泛型.

function inputFn<T>(input: T): T {
  return input;
}
inputFn<string>(123)  //Argument of type '123' is not assignable to parameter of type 'string'.
inputFn<string>('123') //'123'

约束泛型:当我们传入类型变量约束泛型,它就得乖乖听话了。

泛型类

class Person<U> {
  name: U;

  constructor(name: U) {
    this.name = name;
  }

  say(what: U): string {
    return `${this.name}: i am ${what}`;
  }
}

const person = new Person('张三');
person.say('程序员'); //张三:i am 程序员
person.say(123); //Argument of type '123' is not assignable to parameter of type 'string'.

自动推断类型变量的类型: 上面因为Person类实例化时传入的是‘张三’字符串,所以调用原型链上方法say只能传入string,否则会报错,是ts根据传入参数的类型推断出U为string。

泛型接口

interface IPerson<T> {
  name: string;
  age: T;
}

const person1: IPerson<number> = {name: '张三', age: 18};
const person2: IPerson<string> = {name: '张三', age: '18岁'};

通过传入不同类型,让接口更灵活。

实践

实现一个element-ui tableColums泛型接口,参数可查阅element-ui columns

type TableColumnType = 'default' | 'selection' | 'index' | 'expand';
type TableColumnFixedType = 'left' | 'right';
type TableColumnAlign = 'left'| 'center' | 'right';

interface ITableColumn<T, U> {
    type?: TableColumnType;
    label?: string;
    align?: TableColumnAlign;
    prop?: keyof U; //keyof可以自行查阅,后面文章会有涉及
    width?: T;
    formatter?: (row: U, column: ITableColumn<T, U>) => string; //泛型接口自身属性也可以引用自身
    selectable?: (row: U, index: number) => boolean;
    fixed?: TableColumnFixedType;
}

interface IParams {
    id: number;
    name: string;
    sex: string;
    age: number;
}

const tableColumns: ITableColumn<number, IParams>[] = [
    {type: "default", label: '姓名', prop: "name", width: 120}, //prop必须按照IParams格式来,否则编译报错
    {type: "default", label: '性别', prop: "sex"},
    {type: "default", label: '年龄', prop: "age"},
];

const tableData: IParams[] = [
    {id: 1, name: '张三', sex: '男', age: 18}
];

上述代码大家可以自行放入编辑器查看,简易实现了一个element-ui tableColums的泛型接口定义(并不是element-ui table-columns全属性,因为只是个案例只写了一部分),通过传入T和U分别控制width的类型和prop的取值约束。

总结

泛型是高级语言最重要的特性,看看各类插件类库源码泛型写的那种高级感,为了让我们的代码逼格更高从泛型开始,可以豪不夸张的说学好typescript的基础就是泛型,所以大家要多多练习泛型的使用哦,好记性不如烂笔头,看懂了不去敲代码过不了多久就会遗忘的,小伙伴们赶紧在自己的项目中把泛型敲进去!!!

个人博客地址,有兴趣的可以看一看