Vue3的响应式API是怎么实现的?(watchEffect篇、上)

228 阅读2分钟

书接上回,前面我们介绍了watch和computed,这篇文章说一下watchEffect。watchEffect和watch非常像,但是watchEffect不用指定要追踪的响应式数据,也不需要指定响应式数据变更时回调,先来看一下文档怎么说:

image.png

可以发现这就是最普通的响应式系统的用法:立即执行一个函数,执行函数的时候响应式依赖会收集这个函数,然后函数的依赖变化后,会触发函数的重新执行

还记得我们之前说watch的时候,watch的第一个参数可以接受一个getter。所以其实watchEffet就是第一个参数为含有响应式变量的getter,第二个参数为null的watch。 所以我们可以给出一个实现:

// 没有第二个参数的watch,也不需要往fn中传入oldValue和newValue
function watchEffect(fn) {
  let getter = fn
  effect(() => {
    getter()
  })
}

不过实际上源码中WatchEffect因为是一种特殊的watch,watch要兼容一下watchEffect的设计:

function watchEffect(fn) {
  watch(fn, null)
}

function watch(source, callback) {
  let getter
  
  if (typeof source === 'function') {
    // 如果source是getter
    // source为getter还可能是watchEffect
    getter = source
  } else if (Array.isArray(source)) {
    // 如果是数组
    getter = () =>
      source.map((val) => {
        // 我只处理了数组中只有普通对象和getter
        if (typeof source === 'function') {
          return val()
        } else {
          traverse(val)
        }
      })
  } else {
    getter = () => {
      traverse(source)
    }
  }

  let value
  let prevValue

  // 前面讲到过,如果lazy为true,
  // 就返回副作用函数的包装函数,这个包装函数返回副作用函数的值
  const effectFn = effect(
    () => {
      // 执行封装好的getter,读取数据
      getter()
    },
    {
      lazy: true,
      scheduler() {
        // 说明是watch
        if (callback) {
          // 调度函数内执行callback
          value = effectFn()
          callback(value, prevValue)
          prevValue = value
        } else {
          // watch为了兼容watchEffect,在调度函数内嵌套执行effectFn()
          effectFn()
        }
      }
    }
  )
  if (callback) {
    // 因为懒执行,所以手动调用进行读取操作建立响应联系
    // 数据变更调用scheduler的时候,会重新拿到新值
    prevValue = effectFn()
  } else {
    // watchEffect首次运行并收集依赖
    effectFn()
  }
}

在上面的代码中,我们根据有没有回调,来判断是watch和watchEffect,就实现了watchEffect。