Vue3 source code analysis (calculated calculated attribute)

Author: Qin Zhiying

preface

In the previous article, we analyzed the whole process of Vue3 response. In this article, we will analyze how the calculated calculation attribute in Vue3 is implemented.

In Vue2, we have a clear understanding of calculation attributes. Vue3 provides a calculated function as the API of calculation attributes. Next, we will analyze the operation process of calculation 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 new ComputedRefImpl(
    getter,
    setter,
    isFunction(getterOrOptions) || !getterOrOptions.set
  ) as any
}
  • At the beginning, the method of function overloading allows the calculated function to accept two types of parameters: the first is a getter function, and the second is an object with get and set.
  • The next step is to initialize the getter and setter functions inside the function according to the parameters of different types passed in. If the parameter of a function type is passed in, the getter is the function and the setter is an empty operation. If the parameter passed in is an object, the getter is equal to the get function of the object and the setter is equal to the set function of the object.
  • A new ComputedRefImpl is returned at the end of the function, and the previously standardized parameters are passed to the constructor.

Let's analyze the ComputedRefImpl constructor.

ComputedRefImpl

class ComputedRefImpl<T> {
  // Cache results
  private _value!: T
  // Recalculate switch
  private _dirty = true
  public readonly effect: ReactiveEffect<T>
  public readonly __v_isRef = true;
  public readonly [ReactiveFlags.IS_READONLY]: boolean
  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean
  ) {
    // Wrap the passed in getter function
    this.effect = effect(getter, {
      lazy: true,
      // Scheduling execution
      scheduler: () => {
        if (!this._dirty) {
          this._dirty = true
          // Distribution notice
          trigger(toRaw(this), TriggerOpTypes.SET, 'value')
        }
      }
    })
  }
  // When accessing the calculated property, the get function is called by default
  get value() {
    // Need recalculation
    if (this._dirty) {
      this._value = this.effect()
      this._dirty = false
    }
    // Dependency collection is performed when accessing. At this time, the side-effect functions accessing the calculation attribute are collected
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

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

The ComputedRefImpl class is internally maintained_ value and_ dirty are two very important private attributes, among which_ value is used to cache the results of our calculations_ dirty is used to control whether the calculation needs to be reproduced. Next, let's take a look at the internal operation mechanism of this function.

  • First of all, the constructor uses the effect function to wrap the incoming getter during initialization (in the previous article, we analyzed that the function of the effect function is to turn the incoming function into a responsive side effect function), but here we passed in some configuration parameters in the effect. Remember this code when we analyzed the trigger function earlier:
const run = (effect: ReactiveEffect) => {
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }
effects.forEach(run)

When the attribute value changes, trigger function will be triggered to distribute and update, loop through all effect functions that depend on this attribute, and use run function to execute effect. If scheduler is configured in the parameter of effect, scheduler function will be executed instead of dependent side-effect function. When the attribute on which the calculation attribute depends changes, the effect of the wrapper getter function is executed back. However, because the scheduler function is configured, the scheduler function is actually executed. In the scheduler function, the getter function of the calculation attribute is not executed to obtain a new value, but to_ Set dirty to false, and then notify the side-effect function that depends on the calculation property to update. When the side-effect function that depends on the calculation property receives the notification, it will access the get function of the calculation property. At this time, it will be updated according to the_ Dirty value to determine whether recalculation is required.

Back to our constructor, we only need to remember three important points in constructor initialization: first, wrap the incoming getter function with the effect function. Second: in the process of using the effect wrapper, we will execute the getter function. At this time, for the accessed properties, the current calculation properties will be collected into the corresponding dependency set. Third: the configuration parameters lazy and scheduler are passed in. These configuration parameters are used to control the scheduling time of the calculation properties when the properties subscribed to by the current calculation properties are changed.

  • Then we continue to analyze get value. When we access the value of the calculated attribute, we actually access the return value of this function, which will be based on_ Dirty to determine whether the getter function needs to be recalculated_ If dirty is true, you need to re execute the effect function and set the value of effect to false, otherwise it will return the previously cached value_ Value value. In the stage of accessing the calculated attribute value, the track function will be called for dependency collection. At this time, the side-effect function of accessing the calculated attribute value is collected, and the key is always vlaue.
  • Finally, when setting the value of the calculation attribute, the set function will be executed, and then we will call the_ setter function.

Sample process

So far, the execution process of calculating attributes has been analyzed. Let's go through the whole process with an example:

<template>
    <div>
        <button @click="addNum">add</button>
        <p>Calculation properties:{{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>

The following is a flowchart. When you click the button in the page to change the value value of testData, the change process is the red line below.

  • First, when initializing the page, testData becomes responsive data after ref(), which will access testData Value for dependency collection, when testData If the value of value changes, the dependent collection that depends on this value will be distributed and updated
  • A getter function is passed into computed. The getter function has a pair of testdata Value. At this time, the side-effect function of the current calculation attribute subscribes to testdata Value, computed returns a value, and the components in the page have access to the computed return value. The rendering side effect function of the page subscribes to the computed return value, so there are two dependent sets in this page.
  • When we click the button in the page, it will change testdata Value. At this time, the side effect function of the subscription calculation attribute will be notified to update. Because we configured the scheduler when generating the side effect of the calculation attribute, we executed the scheduler function. The scheduler function did not immediately execute the getter function for recalculation, but the private variable inside the ComputedRefImpl class_ Set dirty to true, and then notify the side-effect function of the current calculated property of the subscription to update.
  • When the rendering side effect function in the component performs the update operation, it will access the get value function, and the function will_ dirty value to determine whether recalculation is required, because the previous scheduler function will_ dirty is set to true, so the side effect function effect of getter function will be called at this time. At this time, the result will be recalculated and returned, and the page data will be updated.

summary

The two biggest features of computing attributes are

  • When the value on which the delay calculation attribute depends changes, the getter function will not be executed immediately to recalculate the new result. Instead, turn on the recalculation switch and notify the side-effect function of the subscription calculation attribute to update. If the current calculation property does not depend on the set, the recalculation logic will not be executed. If there is a dependency that triggers the get of the calculation property, this will be called at this time Effect() is recalculated.
  • Cache results when the dependent properties have not changed, the access calculation properties will be returned to the previous cache_ Value in value.

Interested in Electron? Please pay attention to our open source projects Electron Playground , get you started, Electron.

We will select some interesting articles and news to share with you every Friday, and come to nuggets to pay attention to our Xiao front end weekly.

We are the front-end technical team of Tal · Xiao blackboard.
We will often share the latest and coolest industry technical knowledge with you.
Welcome Know,Nuggets,Segmentfault,CSDN,Jian Shu,Open source China,Blog Garden Pay attention to us.

Tags: html

Posted by Michael001 on Mon, 02 May 2022 07:22:44 +0300