乐趣区

为vue3学点typescript-泛型

往期

第一课, 体验 typescript

第二课, 基础类型和入门高级类型

插一课

本来打算接着上节课, 把高级类型都讲完, 但是写着写着我发现高级类型中, 有很多地方都需要泛型的知识, 那么先插一节泛型.

什么是 ” 类型变量 ” 和 ” 泛型 ”

变量 的概念我们都知道, 可以表示任意数据, 类型变量 也一样, 可以表示 任意类型:

// 在函数名后面用 "<>" 声明一个类型变量
function convert<T>(input:T):T{return input;}

convert中参数我们标记为类型 T, 返回值也标记为T, 从而表示了: 函数的输入和输出的类型一致. 这样使用了 ” 类型变量 ” 的函数叫做 泛型函数 , 那有 ” 泛型类“ 吗?

注意 : T 是我随便定义的, 就和变量一样, 名字你可以随便起, 只是建议都是大写字母, 比如U / RESULT.

泛型类
class Person<U> {
    who: U;
    
    constructor(who: U) {this.who = who;}

    say(code:U): string {return this.who + ':i am' + code;}
}

在类名后面通过 ”<>” 声明一个类型变量 U, 类的方法和属性都可以用这个U, 接下来我们 使用下泛型类:

let a =  new Person<string>('詹姆斯邦德');
a.say(007) // 错误, 会提示参数应该是个 string
a.say('007') // 正确

我们传入了类型变量 (string), 告诉 ts 这个类的Ustring类型, 通过 Person 的定义, 我们知道 say 方法的参数也是 string 类型, 所以 a.say(007) 会报错, 因为 007 是 number. 多以我们可以通过 传入类型变量来约束泛型.

自动推断类型变量的类型

其实我们也可以不指定类型变量为 string, 因为 ts 可以根据实例化时传入的参数的类型推断出Ustring类型:

let a =  new Person('詹姆斯邦德');
// 等价 let a =  new Person<string>('詹姆斯邦德');
a.say(007) // 错误, 会提示参数应该是个 string
a.say('007') // 正确
泛型方法

其实方法和函数的定义方式一样:

class ABC{// 输入 T[], 返回 T
    getFirst<T>(data:T[]):T{return data[0];
    }
}
泛型是什么?

说实话 ts 的文档我找了好几遍, 也没看到他给 泛型 正式做定义, 只是表达他是一种描述多种类型 (类型范围) 的格式 , 我觉得有点抽象, 我用自己的理解具象下: 用动态的类型( 类型变量 ) 描述函数和类的方式.

泛型类型

我们可以用 类型变量 去描述一个 类型 (类型范围), ts 的数组类型Array 本身就是一个泛型类型, 他需要传递具体的类型才能变的精准:

let arr : Array<number>;
arr = ['123']; // 错误, 提示数组中只可以有 number 类型
arr = [123];

下面我们自己定义一个泛型类型, 就对开头的 convert 函数定义:

function convert<T>(input:T):T{return input;}

// 定义泛型类型
interface Convert {<T>(input:T):T
}

// 验证下
let convert2:Convert = convert // 正确不报错

泛型接口

通过传入不同的类型参数, 让属性更灵活:

interface Goods<T>{
    id:number;
    title: string;
    size: T;
}

let apple:Goods<string> = {id:1,title: '苹果', size: 'large'};
let shoes:Goods<number> = {id:1,title: '苹果', size: 43};

扩展类型变量(泛型约束)

function echo<T>(input: T): T {console.log(input.name); // 报错, T 上不确定是否由 name 属性
    return input;
}

前面说过 T 可以代表任意类型, 但对应的都是基础类型, 所以当我们操作 input.name 的时候就需要标记 input 上有 name 属性, 这样就相当于我们 缩小了类型变量的范围, 对泛型进行了约束:

// 现在 T 是个有 name 属性的类型
function echo<T>(input: T extends {name:string}): T {console.log(input.name); // 正确
    return input;
}

一个泛型的应用, 工厂函数

function create<T,U>(O: {new(): T|U; }): T|U {return new O();
}

主要想说 3 个知识点:

  1. 可以定义多个类型变量.
  2. 类型变量和普通类型用法一直, 也支持联合类型 / 交叉类型等类型.
  3. 如果一个数据是可以实例化的, 我们可以用 {new(): any} 表示.

不要乱用泛型

泛型主要是为了约束, 或者说缩小类型范围, 如果不能约束功能, 就代表不需要用泛型:

function convert<T>(input:T[]):number{return input.length;}

这样用泛型就没有什么意义了, 和 any 类型没有什么区别.

总结

泛型是编译型语言最重要的特性, 泛型写的好就会让人觉得代码很高级, 可以说泛型是一个成手 ts 程序员必须熟练的技巧, 面试的时候是加分项, 所以大家写代码多多用泛型练习哦, 加油ヾ(◍°∇°◍)ノ゙, 下面是的用 ts 写的几个小项目, 写的不好, 就是有份热情, 抛砖引玉, 大家肯定能写出更好的:

手势库: https://github.com/any86/any-…

命令式调用 vue 组件: https://github.com/any86/vue-…

工作中常用的一些代码片段: https://github.com/any86/usef…

一个 mini 的事件管理器: https://github.com/any86/any-…

退出移动版