往期
插一课
本来打算接着上节课, 把高级类型都讲完, 但是写着写着我发现高级类型中, 有很多地方都需要泛型的知识, 那么先插一节泛型.
什么是”类型变量”和”泛型”
变量的概念我们都知道, 可以表示任意数据, 类型变量也一样, 可以表示任意类型:
// 在函数名后面用"<>"声明一个类型变量
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这个类的U
是string
类型, 通过Person
的定义, 我们知道say
方法的参数也是string
类型, 所以a.say(007)
会报错, 因为007是number
. 多以我们可以通过传入类型变量来约束泛型.
自动推断类型变量的类型
其实我们也可以不指定类型变量为string
, 因为ts可以根据实例化时传入的参数的类型推断出U
为string
类型:
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个知识点:
- 可以定义多个类型变量.
- 类型变量和普通类型用法一直, 也支持联合类型/交叉类型等类型.
- 如果一个数据是可以实例化的, 我们可以用
{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-…