4.9 watch的实现原理

139 阅读2分钟

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

4.9 watch的实现原理

在实现完computed之后,作者再把目光移向了watch。我们知道watch就是监听一个响应式数据,它的基本用法如下

const obj = reactive({a:1})
watch(obj,()=>console.log('obj changed'))
obj.a++

不记得在哪里看过,说vue中要尽量少用watch

其实watch确实也不是必须的,vue作为一个mvvm框架,大部分工作都由其中的vm处理了,也就是viewModel.比如说你method中改变了data,那么对应于template中也会变化,这部分就是由viewModel处理了,因此这时候无需watch。

再比如一个经典的例子,输入框调接口搜索。我们可以在change事件中去调接口,同时记得加上防抖或节流。这时候也不需要watch。

言归正传。我们怎么使用之前已经实现过的方法组合为watch呢。

首先,我们肯定是要使用调度器的

function watch = (source, cb) => {
  effect(
       // 触发读取,收集依赖
      ()=>source.a,
      //调度器
      scheduler(){
        cb()
      }
  )

}

这时候,我们发现,必须要读取才能手机依赖,那么读取的字段就需要从source中获取,那么我们如何知道读取哪些字段呢,而不是像现在这样,写死成了a.换句话说,我们现在这个watch函数不具有通用性.

这时候我们需要手动去收集依赖,同时,需要注意的是,source还有可能是一个getter函数

function traverse(value,seen = new Set()){
    const typeName = typeof value
    // 首先判断value的类型
    if(typeName === 'function'){
      return value
    }
    if(typeName !=== 'object' || value === null || seen.has(value)){
     return 
    }
    // 将触发过的值放入Set中,避免多次触发
    seen.add(value)
    // 这里暂不考虑数组,map等等,其实这里会有相当多的逻辑判断
    
    for(const k in value){
        // 递归遍历,触发所有层级的依赖
        traverse(value,seen)
    }
    return value
}

这样就基本完成了一个转换source,触发依赖的函数,我们用它替换source.a

function watch = (source, cb) => {
  effect(
       // 触发读取,收集依赖
      traverse(source),
      //调度器
      scheduler(){
        cb()
      }
  )

}

我们还知道,watch函数还可以获取旧值和新值,这时候就需要lazy这个属性了.这部分,我建议大家自己思考一下,如何使用lazy。我大概提示一下,我们手动执行effect,就能获取旧值,effect自己执行就能获取新值,这里还有一个小坑,当你们自己写就知道了