通过响应式配置项来实现computed、watch !

295 阅读4分钟

概述

computed、watch这两个api在响应式系统是衍生的api,通过proxy实现的响应式逐渐改良,利用缓存及动态监听、同时考虑底层性能优化,相比React的callback、memo等的手动性能优化,vue放开了开发者的双手。

为什么需要调度器?

首先调度器是什么? 调度器是就是调配响应式数据的中心,叫scheduler。本质就是一个回调函数,内部单独处理当前存在的响应式依赖的数据集合的方法。简单理解为一个options配置项。用scheduler我们可以实现对应的数据懒加载,执行顺序的控制,控制执行次数优化性能,同时也抽离代码实现复用。

调度器的简单实现:

effect(() => {
  console.log("obj.text");
},  {
  scheduler(fn) {
       
  }
})

这里的effect副作用函数相当于响应式对象在读取的时候存入的依赖,一旦监听的对象发生变化,那么effect会被执行。可以简单理解为computed内部挂载的函数就是响应式数据的不同依赖集合。调度器本质在内部通过effect函数内部传入一个scheduler回调,那么意味着每个依赖的逻辑内都有对应的配置项,我们只要监听变化后遍历这个effect。通过Promise的微任务机制来控制执行顺序,用set结构去重防止多次重复执行。

computed、lazy、scheduler是如何配合的?

computed就是计算属性,可以实现缓存,他的本质其实就是依赖其他响应式数据的变化而变化,其本身是被动更新的数据。computed的内部实现其实通过配置scheduler的lazy变量来控制effect执行, 通过回调返回effect,以手动的方式在外部实时监听变化,一旦变化执行此effect函数。同时内部也会被标记dirty,意味着此时对这些依赖会实时监听并渲染。

实现computed !

function computed(getter) {
  let value
  let dirty = true //检查是否被依赖

  //下面等同于你缓存的函数,等同于computed内部的fn()
  const effectFn = effect(getter, {
    lazy: true, //延迟执行,本质不立马执行effect,通过return暴露方法再执行其实等同于延迟执行
    scheduler() {
      //通过这个变量控制是否需要根据依赖变化更新,本质就是一个优化
      if(!dirty) {
        dirty = true
        trigger(obj, 'value')
      }
    }
  })

  const obj = {
    get value() {
      //一旦属性对读取,那么得到对应暴露的effect副作用函数
      if(dirty) {
        value = effectFn()
        dirty = false
      }
      //当读取value,手动调用追踪
      track(obj, 'value')
      return value
    }
  }

  //返回这个对象,这个对象包含了计算属性,计算属性包含了对应的effect副作用函数
  return obj
}

watch实现为什么需要调度器?

watch本质监听数据变化而变化,会触发scheduler内的这个函数,函数可以传入oldVal,newVal以及immediate立即执行属性,deep属性。那么就需要用一个调度器来配置响应式。这些控制本质在收集依赖的effect内部挂载了对应的属性,如果一旦内部存在这个属性那么就执行watch的回调。

watch、immediate原理是啥?

watch实现原理就是一旦set监听到数据改变,那么调度器scheduler在effect副作用函数中执行对应的watch回调。 immediate本质就是一个开关,只要get的时候,我的scheduler内部存在immediate就主动执行当前的effect。

实现watch !

//source代表监听数据,cb是回调,option配置deep、immediate
function watch (source, cb, options = {}) {
  let getter //将source的响应式数据改造成副作用函数
  if (typeof source === 'function') {
    getter = source
  } else {
    getter = () => traverse(source)
  }

  let oldValue, newValue

  //cleanup存储过期的回调
  let cleanup
  function onInvalidate (fn) {
    cleanup = fn
  }

  const job = () => {
    newValue = effectFn()
    if (cleanup) {
      cleanup()
    }
    cb(oldValue, newValue, onInvalidate)
    oldValue = newValue
  }

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

  if (options.immediate) {
    job()
  } else {
    oldValue = effectFn()
  }
}

总结

简单总结下,computed利用lazy暴露出effect函数,手动执行达到被动延迟响应。watch在set的时候利用scheduler的回调去执行,deep就是递归对象的属性监听的操作,immediate也不过就是一个变量控制是否立即执行,因为默认lazy是延迟的。