Author: Qin Zhiying

preface

In the last article we looked at the overall flow of Vue3 responsiveness, and in this article we’ll look at how computed properties in Vue3 are implemented.

We already know the calculation of attributes clearly in Vue2, and Vue3 provides a computed function as an API for calculating attributes. Let’s analyze the operation process of calculating attributes from the perspective of source code.

computed

export function computed<T> (getter: ComputedGetter<T>) :ComputedRef<T>
export function computed<T> (
  options: WritableComputedOptions<T>
) :WritableComputedRef<T>
export function computed<T> (
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  return newComputedRefImpl( getter, setter, isFunction(getterOrOptions) || ! getterOrOptions.set )as any
}
Copy the code
  • The way function overloading is used in the beginning allowscomputedFunctions take two types of arguments: the first is a getter function, and the second is a bandgetandsetThe object.
  • The next step is to initialize the inside of the function based on the different types of arguments passed ingetterandsetterFunction, if you pass in an argument of a function type, then the getter is that function, the setter is an empty operation, if you pass in an object, then the getter is equal to the get function of that object, and the setter is equal to the set function of that object.
  • One is returned at the end of the functionnew ComputedRefImpl, and passes the previously normalized parameters to the constructor.

Let’s examine the ComputedRefImpl constructor.

ComputedRefImpl

class ComputedRefImpl<T> {
  // Cache the resultsprivate _value! : T// Recalculate the switch
  private _dirty = true
  public readonly effect: ReactiveEffect<T>
  public readonly __v_isRef = true;
  public readonly [ReactiveFlags.IS_READONLY]: boolean
  constructor(getter: ComputedGetter
       
        , private readonly _setter: ComputedSetter
        
         , isReadonly: boolean
        
       ) {
    // Wrap the getter function passed in
    this.effect = effect(getter, {
      lazy: true.// Schedule execution
      scheduler: () = > {
        if (!this._dirty) {
          this._dirty = true
          // send notifications
          trigger(toRaw(this), TriggerOpTypes.SET, 'value')}}})}// The default get function is called when accessing calculated properties
  get value() {
    // Whether recalculation is required
    if (this._dirty) {
      this._value = this.effect()
      this._dirty = false
    }
    // The dependency collection is collected when accessing the side effect function of the calculated property
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newValue: T) {
    this._setter(newValue)
  }
}
Copy the code

The ComputedRefImpl class internally maintains two very important private attributes, _value and _DIRTY, where _value is used to cache the results of our calculations and _dirty is used to control whether or not we need to reproduce the calculations. Let’s take a look at the inner workings of this function.

  • First, the constructor is used during initializationeffectFunction pair pass ingetterA layer of packaging was carried out(In our previous article, we analyzed the effect function's function to turn an incoming function into a responsive side effect function.)But here we pass in some configuration parameters in effect. Remember that we had this code when we analyzed the trigger function:
const run = (effect: ReactiveEffect) = > {
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }
effects.forEach(run)
Copy the code

When the attribute value is changed, trigger function will be triggered to distribute updates, and all effect functions that depend on this attribute will be cycled through, and effect will be executed by run function. If the parameter of effect is configured with scheduler, the scheduler function will be executed. Instead of performing a dependent side effect function. Effect, which wraps the getter function, is executed when the property that the calculated property depends on changes, but because scheduler is configured, the scheduler function is actually executed. The scheduler function does not execute the getter for the calculated property to fetch the new value. Instead, it sets _dirty to false and then notifes the side function that depends on the calculated property to update. When the side function is notified, it accesses the GET function. The _dirty value is used to determine whether recalculation is required.

Going back to our constructor, just remember that we initialized the constructor with three important points: first: wrap the getter passed in with the effect function. Second, in the process of using effect wrapper, we will execute the getter function, and then the getter function will collect the current calculated property into the corresponding dependency set for the accessed property. Third: The configuration parameters lazy and scheduler are passed in, which control the timing of the scheduling of a compute property when the current one’s subscribed property changes.

  • And then we go onget valueWhen we access the value of the evaluated property, we are actually accessing the return value of this function, which will be based on_dirtyTo determine if the getter needs to be recalculated,_dirtyTrue requires the effect function to be re-executed and theeffectIs set to false, otherwise the previously cached one is returned_valueValue. Called during the access phase for the evaluated property valuetrackFunction for dependency collection, which is the side effect function that accesses the calculated property value, and the key is always vlaue.
  • And finally, when we set the value of the evaluated property, we execute the set function, and then we call the one we passed in_setterFunction.

Sample process

At this point, the execution process of calculating attributes is analyzed. Let’s go through the whole process with an example:

<template>
    <div>
        <button @click="addNum">add</button>
        <p>{{computedData}}</p>
    </div>
</template>

<script>
import { ref, watch,reactive, computed } from 'vue' 
import { effect } from '@vue/reactivity'
export default {
  name: 'App'.setup(){
    const testData = ref(1)
    const computedData = computed(() = > {
      return testData.value++
    })
    function addNum(){
      testData.value += 10
    }
    return {
      addNum,
      computedData
    }
  },
}

</script>
Copy the code

Here is a flow chart. When you click on a button on the page to change the value of testData, this is the red line below.

  • When the page is first initialized,testData becomes responsive data after ref(), and dependencies are collected on the values accessing testData.value. When the value of testData.value changes, updates are distributed to the set of dependencies that depend on that value
  • If a getter function is passed in computed, and testData.value is accessed internally, the side effect function of the current computed property subscribed to the value of testData.value, computed returns a value, The component in the page has access to computed return values, and the page’s render side effect function subscribs to computed return values, so there are two dependency collections in the page.
  • When we click the button in the page, it changes the value of testData.value, which notifies the side effects function that subscribed to the calculated properties to update, since we configured it when we generated the calculated properties side effectsscheduler, so the implementation isschedulerThe function,schedulerThe function does not immediately execute the getter to recalculate, but instead willComputedRefImplA private variable inside a class_dirtySet to true, and notify the side effect function that subscribed to the current calculated property to update.
  • The render side function in the component is accessed when the update operation is performedget valueThe _dirty function is used internally to determine if it needs to be recalculatedschedulerThe function sets _dirty to true so effect, the side effect of the getter, is called, and the result is recalculated and returned, and the page data is updated.

conclusion

The two biggest features of computed properties are

  • When the value on which the delayed calculation property depends changes, the getter function is not immediately executed to recalculate the new result. Instead, the recalculation switch is turned on and the side effect function subscribed to the calculated property is notified to update. The recalculation logic is not performed if the current evaluated property has no dependency collection, and this.effect() is called only if there is a dependency on a GET that triggers the evaluated property.
  • Cache the resultsWhen the dependent property has not changed, the access-evaluated property is returned to the previous cache_valueThe value.