一、前言
最近学习TS,被复杂的类型体操搞的比较晕, 经常想学了那么高深的体操后是不是有用武之地,技术的学习应该是要以落地为目的,要不然很快就忘记了, 其实我在业务开发中用ts用的用的比较浅, 想框架源码中那么高深的ts,我暂时还没有用到过, 还没到给框架提merge的水平, 其实用ts最多的还是方法类库,毕竟经常打交道,而且同事很大可能会用你写的方法, 所以学着给方法加TS,是个不错的选择
lodash
是这个不错的学习对象, 他的源码其实是js 写的, ts的支持是 @types/lodash
这个npm包中给声明的, 所以我们可以学着这个库给lodash 的方法添加 ts
这个库很大, 找起来很不方便, 强烈推荐 一个浏览器插件 Sourcegraph
安装上这个之后,在刷新,github页面会有一个按钮,点击会跳转打开一个带有树状层级,可以自由检索内容的页面
可以 command + P
搜索 pick 就很容易能找到想找的内容
二、实现
1、pick 的基本用法
先看看pick的基本用法, 打开 lodash 官网, 链接
var object = { 'a': 1, 'b': '2', 'c': 3 }; _.pick(object, ['a', 'c']); // => { 'a': 1, 'c': 3 }
我们就接下来就用简单的代码实现上面的例子,并给方法添加ts, 忽略lodash _.pick 中的细节,以及方法的兼容性 写法 等问题
function pick(target={}, keys) { let result = {}; Object.keys(target).map((el) => { if (keys.includes(el)) { result[el] = target[el]; } }); return result; } var result = pick({ a: "1", b: "2", c: 3 }, ["a", "c"]); // => { 'a': 1, 'c': 3 }
代码比较简单, 传入两个参数, 一个是对象, 一个是对象的key的数组, 返回一个对象
2、给 pick函数 添加 ts
添加ts 之前 我们需要先明白 为什么要给这个函数添加 ts
其实是跟这个函数的作用 有关的, 这个函数 用就是通过传入对象和对象 key的数组,去匹配出,想要的值并组成 对象返回
所以ts要做的就是以下几点
- 1、基础版 —— 基础的是,约束函数的入参和返回值(入参是对象和数组,返回值是对象)
- 2、进阶版 —— 因为两个入参是有关联关系的, 最好能体现出关联关系(keys 中的值要约束成 target对象中的键 )
- 3、最终版 ——对返回值能约束的更准确, 这句话的意思就是说, 在返回的对象中, 能明确知道取值后的类型,比如返回的是
result = {a:'1'}
, 那么 result.a 应该能推断出来是 string 类型的, 同理 result.c 是数字类型的, 目的就是方便接下了的操作,避免因为考虑不到,调用了不属于该类型的方法导致出现错误, 一言以蔽之,就是约束的类型约准确, 编辑器的提示就能更准确, 就更能避免错误到运行时才发现
1. 实现基础版
下面我们先给pick添加最简单最弱的约束
function pick(target: object, keys: any[]): object function pick(target={}, keys) { let result = {}; Object.keys(target).map((el) => { if (keys.includes(el)) { result[el] = target[el]; } }); return result; }
我们可以在不改动函数的前提下,在写一个函数的规定入参与返回值,也可以写多个, 叫函数的重载
写完这个之后, 再使用的时候, 编辑器就开始提示, 这里需要两个入参,一个对象,一个数组,返回的是一个数组
写到这里就完成了最基本的一步, 约束函数的入参和返回值, 能保证基本的用法不出错了, 接下了就是更进一步的完善,让提示的内容更多,约束更强,后续的使用更清晰
我们完成第一步的基本后, 两个参数还是没有任何关联的状态, 其实第二个入参,应该约束为第一个入参的key, 然后我们开始给函数使用泛型
2. 实现进阶版
function pick<T extends object>(target: T, keys: (keyof T)[]): T
解释: extends 有两个种用途, 一是约束,上文中所写,约束定义的 T 是一个 object类型, 而是添加判断 extends ? true: false keyof T : 意思是取T的索引key , keys 意思就是约束为object对象的索引组成的数组
完成这步之后, 再使用的时候,编辑器就会提示我们第二个入参要是第一个入参的key, 这个目标就完成了, 如果输入了对象中没有的key, 那么就要报错提示了
完成了第二步,然后我们继续处理返回结果, 目前的返回结果是T, 意思就是传入的object的类型, 并不是返回对象的类型, 我们需要继续改进,让返回结果也能正确提示处理
上图中, 其实返回结果取值后的类型已经是正确的了, 但是存在一些小问题, 当result取c的时候, 不会报错, 接下来我们需要让取c报错, 只能取约束的key
3. 实现最终版
这时我们需要再加一个泛型参数来表示第二个入参的类型
function pick<T extends object, U extends keyof T>(target: T, keys: U[]): { [P in U]: T[U] }
解释: 在之前基础上,新增一个泛型参数 U 约束为 T 的索引类型组成的联合类型, keys 自然就是 这个联合类型组成的数组, 在返回值上, 返回的是一个对象, [P in U] 意思就是 取联合类型的 值作为新的索引 值 T[U] 可以理解为 target对象内 U 的 value
此时就完成了 给这个简单函数增加ts的能力, 然后再看一下, @types/lodash
中怎么写的
function pick<T extends object, U extends keyof T>( object: T, keys: readonly U[] ): Pick<T, U>;
其实看起来我们的已经很接近了, 只是返回值这里, 库中直接用的ts中的封装好的Pick方法,意思就是从T中匹配U来
type Pick<T, K extends keyof T> = { [P in K]: T[P]; }
看一下ts中Pick的实现, 大致和我们pick函数类似, 但是他是处理类型的,从一个对象类型中,拿取指定的类型
三、总结
现在ts很流行,很多框架库中都支持了TS,但是对于我们普通的开发者,很少接触那么高深的 TS 类型编程, 业务中也总是由于忙于开发, 常常写成了 anyscript 工程师, 看到类型提示错误后, 心中一万匹xx掠过, 常常不知道为什么写ts , 感觉只会限制我们拔刀的速度, 于是就很容易陷入,太难的看不懂, 简单的不屑写的境界, 但是类库方法的ts支持对我们日常开发,是非常重要的, 毕竟业务代码大概率别人用不到,但是方法代码,还是很容易就用到的, 还是需要用心去写, 那么作为类库中大家熟知的lodash就是很好学习的目标, 我们给pick方法添加ts, 从基础版,简单约束传参与返回值后,一步步进阶学习了泛型,以及基于泛型的类型编程, 然后对比 @types/lodash
的实现,顺便学了一下 Pick
这个Ts 内置的高级类型