You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// infer 用于条件判断中,infer R 就是声明一个变量来承载传入函数签名的返回值类型, 简单说就是用它取到函数返回值的类型方便之后使用.typeReturnType<Textends(...args: any[])=>any>=Textends(...args: any[])=> infer R ? R : any
constarg: any={};Object.defineProperty(arg,'state',{get: ()=>this.getState()[moduleName],set: ()=>invariant(false,'You should not set state arg'),});selectors.call(this,arg.state);
constargs=newProxy({},{get: (target,propKey)=>{switch(propKey){case'state':
returnnewProxy({},{get: (target,propKey)=>{returnReflect.get(this.getState()[moduleName],propKey);},set: (): any=>{invariant(false,'You should not set state arg');},});case'rootState':
returnnewProxy({},{get: (target,propKey)=>{returnReflect.get(this.getState(),propKey);},set: (): any=>{invariant(false,'You should not set rootState arg');},});case'rootSelect':
returnthis.select;default:
invariant(false,'You can get state/rootState/rootSelect prop only');}},set: (target,propKey): any=>{invariant(false,'You should not set %s arg',propKey);},});
本文写的主要是自己在做一个小功能的时候所学到的知识。
这个功能是为公司的一个React框架中的数据存储添加的,该套数据存储方案类似于
dva
,而我要做的是为其添加一个形如Vuex
中的getter
功能,根据store来衍生出一些计算出来的状态
,暂且记为select
,同时该select要提供一个memo
函数,用法诸如react hooks
中的useMemo
,用法如下:拿到那需求就先去进行调研,由于该框架是由TS写的,自己虽然之前看过一点TS,但并没有实际项目中使用,
-_-|||
,所以就去过了一遍TS的文档,但在写代码的还是遇到不少坑,后面会讲到。其次是要实现memo函数,所以就去看了下reselect
和memoize-one
,代码解析见reselect与memoize-one主要源码解析。一开始感觉没啥难度,主要就感觉TS不熟,但还是太年轻。TS类型判断
由于要实现在调用select时要给类型提示,形如:
由于第一次在项目中写TS,所以想着什么都给其定义好类型,如上面的selectors,在使用者书写时也能给到提示,虽然理想很美好,但现实很骨感😑。
先来看select调用时的类型提示:
由于select是一个对象,同时select又是根据模块来划分的,并最终汇集到store中,所以很容易来想到,遍历store中的各个模块来进行提示,我也这么做了:
然后第一个坑点出现了,关于其返回值即上述
:
右边该如何写?我想着,stores[storeKey]['selectors']
不是一个函数吗,(也可能是undefined
),所以我再定义一个泛型来返回其函数的返回值不就OK了?接着就出现了如下代码:
它提示我 Selector 不满足
(...args: any[]) => any
,wtf,,后来想了想也对,Selector
也可能是undefined
啊,顺带我也看了下ReturnType的声明定义:既然可能是undefined的问题,那我就直接约束Selector为一个函数,即为每个模块的selector定义声明
ModuleSelector
,然后让Selector 约束为ModuleSelector
:他还是报错,还是不对,同时上面出现了循环依赖,由于第一次写这种声明,也就是想到什么就写什么,,结果就是一堆bug。这时就想到去看下之前代码中的Selector是如何定义的,可以看出他是any:
所以我将其默认值改成一个函数返回一个对象:
但这样报错更多了:
所以还是
undefined
的问题😞,很烦,然后没有一点思路。来到第二天早上,继续想这个问题,然后试着试着,竟然OK了,而且代码并没有我之前想的复杂:这里的重点是如果S约束为any,selectors就是类型S,虽然不太清除为何这样就能排除上述的那种bug,(如有知情者,欢迎告知)。
玄学
,真是玄学,同时也感叹ts提示
真是厉害selectors函数
解决完类型提示后,memo函数我就直接参照上面说的两个库,利用闭包来写,以为没问题了,就开始写单测,这一测就发现这个selectors根本没法用,当一个reducer改变state之后,它根本检测不到state的变化,也就是说,这个state还是一开始调用selectors时的state。
这里做了一层代理,获取最新的state,可是我把问题想简单了,这个代理只能是一层,但此时state已经被赋值给一个变量,也就是说该state代理在state获取属性的时候根本不监测不到,
Proxy
同理。所以就换了一种传参模式直接将arg传入:这样虽然能解决问题,但增加了书写的繁琐,以及对于习惯了对象结构的人来说,不习惯,所以还得继续想办法,之前提到了,defineProperty/Proxy都只能代理一层,那我是不是也可以把State也代理了,即两层代理:
这样在获取state属性时能获取到最新的值了,但这样也会有一个
问题
,就是单独用这个State时,是一个代理对象
,而不是最新的State对象,应该很少会有直接使用State的情况吧(挖坑中😶😶...)关于上述有更好的解法欢迎沟通😉
单测
自己之前很少写单测,这次单测使用的是
jest
,所以就网上看了一下教程就依葫芦画瓢开始写了,下面是关于memo的单测:可见堆了一堆代码,同时也声明了4个
jest.fn
当然好的测试应该一个测试测一个功能点,应该将上面拆为4个测试点。
总结
至此,该小功能算是完成了,但其中还是有些疑惑,自己还是要继续熟悉TS,感受到了TS的强大,同时要多写单测,能发现潜在的问题。
参考
ts 官方文档
TS 一些工具泛型的使用及其实现
jest 官方文档
测试框架 Jest 实例教程
The text was updated successfully, but these errors were encountered: