Vue3 响应式系统 - 3 - Watch实现思路

1. watch实现原理

所谓的watch,就是监听某数据的变化,从而触发一个指定的函数执行。举个例子:

watch(data, () => {
  console.log('data 改变了')
})

data.a++

假设data是响应式数据,那么watch函数的作用就是监控data,当data改变时会调用传入的fn,打印出“data 改变了”。事实上watch函数的实现就是利用了effect函数以及options.schduler选项。

const watch = (obj, watchCb) => {
  effect(() => obj.a, {
    scheduler() {
      watchCb()
    }
  })
}

const data = reactive({
  a: 1,
  b: 2,
  c: 3
})
watch(data, () => {
  console.log('data 改变了')
})
data.a++

在watch函数中,effect函数的第一个参数fn主要目的是读取obj.a,把fn这个依赖收集到obj.a的“桶”里。那么当obj.a改变的时候,就会通过trigger函数把“桶”里的fn.scheduler执行一次,我们在scheduler中执行一次watchCb,watch看似完成了,但如果改变data.b或data.c就不会生效了,原因很明显obj.b和obj.c的get方法没有触发,也就不存在依赖的收集。所以我们需要在effect函数的第一个参数fn中将传入obj的所有属性 进行遍历访问。

const watch = (obj,  watchCb) => {
  const traverse = (data) => {
    if (typeof data === 'object') {
      for (const key in data) {
        traverse(data[key])
      }
    }
  }

  effect(() => traverse(obj), {
    scheduler() {
      watchCb()
    }
  })
}

2. 获取oldValue和newValue

目前的代码已经可以简单的实现watch,但我们在使用watch的时候可以在回调函数中拿到oldValue和newValue,就像这样:

watch(data, (nv, ov) => {
  console.log('ov:',ov)
  console.log('nv:',nv)
})

那么这个nv和ov应该如何得到呢,这又需要利用effect的lazy选项了。

const watch = (data, watchFn) => {

  let oldVal

  const traverse = (data) => {
    if (typeof data === 'object') {
      for (const key in data) {
        traverse(data[key])
      }
    }

    return JSON.parse(JSON.stringify(data))
  }

  const effectFn = effect(() => traverse(data), {
    lazy: true,
    scheduler() {
      const newVal = effectFn()
      watchFn(newVal, oldVal)
      oldVal = newVal
    }
  })

  oldVal = effectFn()
}

在这段代码中,我们声明变量effectFn并为它赋值为effect函数的返回值,因为加了lazy选项后effect传入的fn将不会自动执行,取而代之的则是oldVal = effectFn(),将oldValue的值先缓存下来。当data中有数据变动的时候,会触发scheduler函数,此时newVal = effectFn(),得到新的data值,随后将两个值传入watch回调中,最后再将newVal的值赋给oldValue。

3. Watch的immediate选项

有时候,我们希望当watch创建的时候就执行一次回调函数,在vue中的写法也比较简单,只需要多加一个immediate选项就能做到,像如下代码:

watch(data, (nv, ov) => {
  console.log('ov:',ov)
  console.log('nv:',nv)
}, {
  immediate: true
})

这个功能简单来说,其实就是在effect执行结束后,oldValue = effectFn()之前执行一次scheduler方法咯,我们发现当scheduler执行结束后,oldValue会被正常赋值,所以oldValue = effectFn()似乎就不需要了。

const watch = (data, watchFn, options = {}) => {

  let oldVal

  const traverse = (data) => {
    if (typeof data === 'object') {
      for (const key in data) {
        traverse(data[key])
      }
    }

    return JSON.parse(JSON.stringify(data))
  }

  const scheduler = () => {
    const newVal = effectFn()
    watchFn(newVal, oldVal)
    oldVal = newVal
  }

  const effectFn = effect(() => traverse(data), {
    lazy: true,
    scheduler
  })

  if (options.immediate) {
    scheduler()
  } else {
    oldVal = effectFn()
  }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值