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 allows
computed
Functions 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 in
getter
andsetter
Function, 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 function
new 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 initialization
effect
Function pair pass ingetter
A 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 on
get value
When we access the value of the evaluated property, we are actually accessing the return value of this function, which will be based on_dirty
To determine if the getter needs to be recalculated,_dirty
True requires the effect function to be re-executed and theeffect
Is set to false, otherwise the previously cached one is returned_value
Value. Called during the access phase for the evaluated property valuetrack
Function 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
_setter
Function.
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 effects
scheduler
, so the implementation isscheduler
The function,scheduler
The function does not immediately execute the getter to recalculate, but instead willComputedRefImpl
A private variable inside a class_dirty
Set 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 performed
get value
The _dirty function is used internally to determine if it needs to be recalculatedscheduler
The 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
_value
The value.