vue3的computed原理

92 阅读1分钟

1、执行下面代码

packages\vue\examples\composition\test.html

<script src="../../dist/vue.global.js"></script>
<div id="demo">
  <h1>
    <header>{{count}}</header>
    <header>{{count2}}</header>
  </h1>
</div>
<script>
  const { createApp, ref, toRefs, reactive, watch, onMounted, computed } = Vue
  var app = createApp({
    setup() {
      var count = ref(1)
      var count2 = computed(() => {
        debugger
        return count.value * 2
      })
      setTimeout(() => {
        count.value++
      }, 1000);
      return { count, count2 }
    }
  })
  app.mount('#demo')
</script>

2、流程分析

  1. 执行setupCompoent时,会执行computed函数
  2. 获取gettersetter
  3. 执行new ComputedRefImpl(getter, setter),内部代码分成下面两个部分:
    1. this.effect = new ReactiveEffect( getter , fn )
      1. count.value++时,则执行effect.schedule,于是执行fn
      2. fn函数先判断this._dirty === false
      3. 如果是,则this._dirty = true,且执行triggerRefValue,否则啥也不干
      4. 执行triggerRefValue时,内部会执行effect.schedule(),于是执行() => queueJob( instance.update )
    2. get value(){ }
      1. componentUpdateFn执行patch时访问到了该计算属性的值,才会触发get value(){ }
      2. 如果触发了get value(){} ,则执行下面步骤:
      3. 执行trackRefValue(),收集上面声明的this.effect 和 响应式数据关联,当count.value++时,触发上面的fn
      4. if(self._dirty === true)
      5. 是则,self._dirty = true , 且self._value = self.effect.run()
      6. 否则,说明self._dirtyfalse,直接返回self._value

processOn

3、代码分析

packages\reactivity\src\computed.ts

function computed(
  getterOrOptions,
  debugOptions
) {
  let getter
  let setter
  const onlyGetter = isFunction(getterOrOptions)
  if (onlyGetter) {
    getter = getterOrOptions
    setter = NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter,isSSR)
  return cRef
}
class ComputedRefImpl {
  public _dirty = true
  constructor(getter , private readonly _setter) {
    this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true
        triggerRefValue(this)
      }
    }) 
  }
  get value() {
    const self = toRaw(this)
    trackRefValue(self)
    if (self._dirty || !self._cacheable) {
      self._dirty = false
      self._value = self.effect.run()!
    }
    return self._value
  }
  set value(newValue) {
    this._setter(newValue)
  }
}