This is the fourth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

preface

As usual, prepare a demo and start with debug

. const data = reactive({ a: 1, b: 2, count: 0 }) const computedVal = computed(() => { return data.a + data.b }) console.log('computedVal =>', computedVal.value) const computedVal2 = computed({ get: () => { return data.a + data.b }, set: (value) => {data.a = value}}) console.log('computedVal2 =>', computedVal2.value) console.log(' second evaluation =>', computedVal2.value) ...Copy the code

Let’s start

The two main things you do with computed functions are to define getters and setters, and to create an instance of ComputedRefImpl and return.

Let’s look at the first one

if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = __DEV__
        ? () => {
        console.warn('Write operation failed: computed value is readonly')
    }
    : NOOP
} else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
}
Copy the code

The getterOrOptions parameter passed in first determines if it is a function and assigns the function to the getter if it is, and if it is, the setter is defined as an empty function. That is, if getterOrOptions is function, then computed is read-only.

If getterOrOptions is not a function, then the get and set in getterOrOptions are assigned to the getter and set. This shows that you can pass in a Handler object when calling Computed, which is the second method of calling in demo.

The second thing

return new ComputedRefImpl( getter, setter, isFunction(getterOrOptions) || ! getterOrOptions.set ) as anyCopy the code

Call the ComputedRefImpl class and pass in the getter and setter. The third argument is read-only or not. If getterOrOptions is functiton or there is no set attribute in getterOrOptions, it will be read-only.

Next go to ComputedRefImpl

ComputedRefImpl class

A few properties were defined first

  • _valuePrivate property that caches the computed results
  • _dirtyPrivate property that identifies whether recalculation is required
  • effectRead-only property, storedreactiveEffectfunction
  • __v_isRefRead-only property, identifies whetherref
  • __v_isReadonlyRead-only property, which identifies whether it is read-only

This.effect and this.__v_isREADOnly are then assigned in constructor, with this.effect corresponding to the return value of effect.

this.effect = effect(getter, { lazy: true, scheduler: () => { if (! this._dirty) { this._dirty = true trigger(toRaw(this), TriggerOpTypes.SET, 'value') } } }) this[ReactiveFlags.IS_READONLY] = isReadonlyCopy the code

This effect function is mentioned in the Vue3 Reactive source learning article. Its role is to wrap the passed function into a Reactive Effect function, which is the dependency we will eventually collect. In the process of calling effect function, activeEffect will be assigned, which can ensure that the dependencies can be correctly collected when calling track function.

The important thing to note when calling effect is that it is judged in the scheduler method! If this._dirty is true, the trigger function is called to trigger the dependency.

Problems encountered

  • Question: Why pass in a scheduler in ComputedRefImpl

    A: It is used to control the timing of computing properties when the properties subscribed to by the current computing properties change

  • Q: The time to call this.effect is when _dirty is true and when _dirty is false in the scheduler function, that is, when there is no need to recalculate the dependency, the trigger function is called. If this._dirty is true when the scheduler function is called, it will not raise the dependency. If this._dirty is true when the scheduler function is called, when will it be called again?

    A: This problem is caused by a lack of understanding of the entire process of collecting and triggering dependencies. This._dirty) {} cause. Here’s why:

    • When in obtainingcomputed.valueI’ve changed it many times beforecomputedIs triggered every time you change the value that is dependent onscheduler.
    • The first time you change itthis._dirtyfalseWill go into the judgment, willthis._dirtyThe assignment fortrueperformtrigger
    • The second time you change it, you don’t need to go to the judgment again, because the Settingsthis._dirtytrueThe reason is to be able to get incomputed.value“Is triggered againthis.effectevaluation
    • So the first time you change the valuethis._dirtySet totrueYou don’t need to set this value again, no matter how many times you change it. I’ll get it againcomputed.valueAs long asself._dirtytrueYou can do it againthis.effectEvaluate to calculate the latestcomputed.

Now I’m going to look at the get function.

get

get value() {
    // the computed ref may get wrapped by other proxies e.g. readonly() #3376
    const self = toRaw(this)
​
    if (self._dirty) {
        self._value = this.effect()
        self._dirty = false
    }
    track(self, TrackOpTypes.GET, 'value')
    return self._value
}
Copy the code

In get, the this.effect function is called only when self._dirty is true, and then self._dirty is set to false. The track function is called outside the condition to collect dependencies and return the calculated result.

Track function is mentioned in the article about learning Vue3 Reactive source code.

Problems encountered

  • Q: what is the value of _dirty and what is the value of get

    const self = toRaw(this)
    if (self._dirty) {
        self._value = this.effect()
        self._dirty = false
    }
    Copy the code

    Answer: Controls whether the computed attribute needs to be recalculated. The value will be false after the first recalculation and the value will be cached. If the value of the evaluated attribute is obtained again without any changes in the dependent attribute, the value of _dirty is false and the cached value is returned.

  • Question: Computed collects dependencies on the properties used. Does track in the get function do that? However, according to the existing demo, the execution of the track function will be returned, because the activeEffect === undefined, then what is the function of calling the track function here.

    A: Both the track and the trigger invoked in the Scheduler are to resolve that there are other dependencies on the computed service.

set

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

The set function simply calls the setter function passed in from the outside and sends back newValue.

conclusion

  • computedThe result of the last calculation is cached, and if the property on which the calculation depends has not changed, access to the property will return the previous one_valueThe cached value.
  • computedThe evaluation of the evaluated property is lazy. The evaluated property is not evaluated immediately when the property on which the evaluated property depends changesschedulerFunction will_dirtyChange totrueAnd call thetriggerFunction tells places that depend on this evaluated property to update. Only those that trigger the computed propertyget valueFunctionthis.effectFunction is recalculated.

Refer to the link

Vue3 source code parsing (computed- computed attributes)