我之前业务代码index.ts只是为了方便我在浏览器调试,并不能成为我代码健壮性的一部分。
在源码中computed与watch,只有computed属于响应式的核心代码,而wacth是在runtime-core这部分代码里面。
承接上文,随着场景的扩展,代码的修改,我已经不能保证我所写的代码对之前的业务是否产生影响,如果每次都跑一下之前的测试页面显然是不现实的。需要通过自动化手段保证代码后续修改的质量。我的场景还是比较简单的就是一个ts的单元测试场景,其实也不用太费劲毕竟尤大已经帮我们写好了单元测试了,最基本的方式就是把他的单元测试拷贝过来,其实大家只要熟悉一定单元测试的使用方式就可以了,可以查看jest官方文档。
个人认为比较好的开发流程如下
单元测试建立完成,新建test脚本命令。
继续回归代码本身
备注:源码里面响应式的代码库中并没有控制多次赋值的情况,这样实现有些硬写,有任务调度的设计,真正任务的调度的具体实现是在核心库有详细的实践,可以理解为下面是调度的实现,但是是无效的代码
调度执行可以是场景优化一种方式。正常的场景下面我们可以监听数据的变化,执行副作用函数,真正的业务场景上面可能需要我们做一些执行优化例如多次赋值的场景。
其实在真正执行过程中,我们不希望都执行以下代码,document.body.innerHTML = 0, document.body.innerHTML = 7 ,document.body.innerHTML = 10...... 最后执行document.body.innerHTML = 70。这个显然是不符合预期的,我们想要优化的执行其实是只执行第一个和最后一个。
这个过程我们可以理解为合并丢弃的过程,这个在数据请求处理场景上面,其实有些异曲同工之妙,比如我向后端发送10个相同的请求,其实正在有意义的是最后一个请求。大家可以想一想如何实现?
当然这个场景相对较简单一点,因为不是异步的场景,因为不需要等待。实现的原理可以使用微任务的特性,这个是前端面试知识点,这是微任务和宏任务的博客,大家如果可以访问国外网站的话,有个视频介绍的也不错 youtobe地址,前端面试八股文了解了解。
技术一定要基于场景这个观点永远不会变。学了这个知识,在现实的开发中得以利用,如果大家开发一个任务调度相关的话,这个知识是有很大帮助的。
实现的原理就把执行的函数放到微任务中,改变函数执行的顺序。核心代码具体代码如下:
因为任务需要支持配置项,所以之前单一激活的副作用函数变量缓存不够用了,要支持配置的话,就需要对象了。核心代码如下:
由于核心缓存对象的变化,依赖收集和触发函数也得有响应的变化了,代码如下:
强调:实现是这样实现的,在当前版本的vue3响应式中并没有这个场景了,在之后我的代码和单元测试中会删除这段
我们先看一段vue3的api具体demo,具体代码如下:
一个段代码的阅读最好的方式,就是先明白输入是什么,输出是什么?我们可以看到computed 输入的是一个副作用函数,输出是一个值,这个值有个特性就是当count变化时plusOne的值也是发生变化。
这是一个由果到因的一个过程,逻辑并不合理。因为正在的开发是有这样的场景,需要我这么设计代码,而不是因为代码长这样,我得这么设计。
我们怎么实现当前开发的述求呢?其实是不是只要访问plusOne.value的时候,再把() => count.value + 1 这个方法执行的返回值加以返回就可以实现了。利用之前effect,只要控制这个effect里面副作用函数执行的时机就可以实现我们当前的述求。我们需要一个lazy的这样的option,这个配置项需要我们控制实现的时机,第一次不执行,而是在返回函数,只有我们获取value值时,才调用执行返回。
数据缓存优化例如我们多次访问的场景以及计算属性嵌套的场景。其实多次访问计算属性的时候不需要重复执行。嵌套计算属性的依赖需要正确的执行,以达到计算属性值变化,依赖的计算属性也会发生变化。
避免多次重复计算,无非最简单的方式缓存之前的结果,光缓存还不够,什么时候更新呢?发生新的值变更时就触发更新缓存。
实现方式比如加个缓存的标志位,如果标志位没有变就用缓存的值,如果标志位变了,就使用新的计算结果。
计算属性的特性需要获取值时返回最新的计算结果,就需要将执行函数保留,方便get value 的时候随时调用。
具体实现如下
因为我没有采取真正的lazy方式,直接执行收集了依赖,当computed的时候,我不需要使用嵌套的方式来达到computed的实现。这个部分我表述的不清晰,是因为这边我的实现也不优雅,没关系,下次优化吧,继续进行下面的代码阅读不能阻塞,毕竟我先实现了,单元测试也过了哈哈。太过纠结于细节,这本书一年都搞不完。
先看一下官方的watch的api使用形式,反推实现,我们需要实现一个响应式的数据,并监听数据的变化,执行相关的回调,返回新旧值。
有了前面computed实现铺垫,我们再去实现这个wacth就简单多了,我们其实只需要关注如何实现新值和旧值的回调就可以了。
场景代码如下
我们去实现一个wacth代码如下
参数调整和代码优化:我们知道vue3官方的wacth的api支持多种参数,例如immediate和flush这样的参数都是对回调执行时机进行控制的。
简单实现一下支持立即执行回调和异步执行回调的场景。
onInvalidate 这个过期副作用还是一个很有用的场景,例如我监听一个输入框的变化,向服务端发送请求,但是请求本身是个异步行为,不同的请求响应时间不同,可能第一个请求,10s之后回来,第二个请求1s之后回来,明显在业务上面我们只需要处理第二个请求。
这样的业务常规的处理方式有两种,第一种就是队列数据,第一个请求处理完成再处理第二个,依次处理下面的请求,第二种就是只要发送新的请求,前面的请求就取消,abort这样的api使用,可能不同的请求库有不同的请求方式,具体场景是由业务决定的。
这样闭包变量的方式也是我们处理异步丢弃的一种实现方案,而不是在请求库,请求方式的层面解决这个问题。
现在的业务上面其实我们是希望最后一个请求数据生效,其他的不处理。
实现方式如下