4.10 立即执行的watch与回调执行时机

90 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

watch其实就是对effect的封装,我们通过封装实现了watch的基本功能,就是当数据变化时,执行相应的回调。

但是,在真正的watch里,还有一个immediate的控制项,用以判断回调是否立即执行。立即执行的意思是在调用watch的时候不管数据变没变化,都先执行一次。因此,我们需要把调度器scheduler再次封装出来,以供提前执行

const watch = (source,cb,options) => {
    let newVal,oldVal
    const getter = typeof source === 'function' ? souce : ()=>traverse(source)
    const scheduler = ()=>{
      newVal = effectFn()
      cb(newVal,oldVal)
      oldVal = newVal //这里就是昨天说的坑了,如果不重新复制,你的旧值就会一直是错的
    }
    const effectFn = effect(
     ()=>getter(),
     {
         lazy:true,
         scheduler
     }
    )
    
    if(options.immediate){
      scheduler()
    } else {
        oldVal = effectFn()
    }
}

这样就实现了立即执行调度器的功能,由于执行的时候其实值应该是不会变化的,因此oldVal===undefined,也是符合逻辑的。

在vue3中,新增了一个控制项,flush就是所谓执行的时机,我们上面的封装,其实是同步的,是运行在主线程上的,也就是说可能会造成阻塞,引起页面的卡顿。

flush==='post'时,意味着我们需要在把调度器scheduler放到微任务中进行,这样的话就不会阻塞主线程,就好比nextTick那样。

这时候我们只需要改造一下scheduler即可,我这里简单写一下,我只把需要修改的那部分提取出来

 const effectFn = effect(
     ()=>getter(),
     {
         lazy:true,
         scheduler(){
             if(options.flush==='post'){
                 Promise.reslove().then(scheduler)
             } else {
                 scheduler()
             }
         }
     }
    )

其实这里还是有问题的,我们只想到了flush==='post',但其实看官方文档可知flush还有这些值'pre' 或 'sync'。其实我觉得'sync'应该是不用处理的,因为现在就是同步执行。

那么如何如何处理'pre'

'pre'表示组件更新更新前执行回调,这个在当前的代码中是完全没办法实现,因为我们使用了浏览器的event loop去处理,但其实,我理解中vue中的渲染并不全是依赖浏览器的event loop,更多的是在其内部维护了队列,这样才能做到批量渲染,才能做到nextTick。因此'pre'其实就是将调度器scheduler压入这个队列的头部,这样才能在组件每次渲染或更新前执行,我目前还不能模拟这一部分,说明我的算法能力还是太弱,对于数据结构的这门课已经基本忘光了。