core idea

The purpose of collecting dependencies is to know which subscribers should be notified to do the logical processing when these reactive data changes and their setters are triggered. A key creates a Dep steward that holds the Watcher associated with the key.

The entrance

When access to data objects triggers their GET methods, so when are those objects accessed? A Watcher incoming updateComponent callback is created during the mount initialization of Vue.

Watcher

  1. Enclosing the getter assignment

Check whether updateComponent is a function, if it is assigned to the getter property. Otherwise, call the parsePath method and it will eventually return a function.

  1. this.get()

We first call pushTarget(this), which essentially assigns dep. target to the current render watcher and presses it (for restoration purposes). Then it executes the getter function, which executes updateComponent, which executes vm._update(vm._render(), hydrating), which executes vm._render() first, Since this method generates render VNodes and accesses data on the VM in the process, the get of the data object is triggered.

pushTarget

get

Each key creates a Dep class. Dep class initialization sets an id: this.id = UI ++ and an array: this.subs = []. That is, the Dep for each key has a different ID. The dep.depend() method is called when get is triggered.

dep.depend()

The dep.target.adddep (this) function is called. We mentioned that dep. target has been assigned to the current render watcher at this time.

Dep.target.addDep(this)

Get the ID of deP, each time addDep will determine whether there is this ID to ensure that no repeated DEP will be added, if there is no final call dep.addSub(this).

dep.addSub(this)

Perform this.subs.push(sub) to subscribe the current watcher to the subs of the deP held by the data in preparation for which subs will be notified when the data changes. At this point the data has been subscribed successfully.

popTarget()

Getting back to this.get(), popTarget basically restores dep. target to its previous state, because the current VM’s data dependency collection has been completed, and the corresponding render dep. target needs to change as well.

this.cleanupDeps()

Given that Vue is data-driven, so every data change is rerendered, the vm._render() method is executed again and gets the data again, so Wathcer initializes two arrays of Dep instances in the constructor, NewDeps represents the newly added array of Dep instances, while DEps represents the last added array of Dep instances.

When the cleanupDeps function is executed, the DEps is first iterated through, the subscription to deP is removed, newDepIds are swapped with depIds, newDeps with DEps, and newDepIds and newDeps are emptied. So why do you need to remove the DEPS subscription? During the dePS subscription process, it is already possible to eliminate the duplicate subscription by id.

Considering one scenario, our template will render different sub-templates A and B according to v-if. When we render A when certain conditions are met, the data in A will be accessed. At this time, we add getter for the data used by A and perform dependent collection. These subscribers should be notified. So if we change the conditions to render template B, and then add getter for the data used by TEMPLATE B, if we do not have the dependency removal process, then I modify the data of template A, it will notify the callback of the subscription of data A, which is obviously wasteful.

Therefore, Vue removes the old subscription every time it adds a new one. This ensures that in the previous scenario, if template A was modified while rendering template B, the A data subscription callback was removed, so there is no waste. This is Vue handling some details.

Summary (Ghost character)

Vue creates a Watcher updateComponent callback (updateComponent = () => {vm._update(vm._render(), hydrating)})

The Watcher constructor first checks whether updateComponent is a function, and if it is a function that assigns a value to the getter property. Otherwise, call the parsePath method and it will eventually return a function. The get function is then called.

3. The get function

  • We call pushTarget(this), which drops the current watcher into the targetStack queue and assigns the current watcher to dep.target.

  • Then execute the getter function, the updateComponent callback, and execute vm._render().

4. Vm._render () will parse the render function to create a VNode and trigger the corresponding get when accessing template data.

5.get

  • First, get the val value, and then determine if dep. target exists, which is clearly the current dep. target

  • Dep.depend () is then called. Ob.dep.depend() will be called if there is a childOb, that is, if the key is an array, then childob.dep.depend () will be called.

  • The Depend function calls dep.target.adddep (this), which calls the addDep function of the Watcher and passes in the Dep class. Note that the Dep class is initialized with an id: this.id = UI ++ and an array: this.subs = []. That is, the Dep for each key has a different ID.

  • The addDep function first obtains the ID of the DEp. Each time the addDep function determines whether there is such an ID to ensure that the deP will not be added repeatedly. If there is no such id, the dep.addsub (this) is finally called. This is the current watcher.

  • AddSub is just throwing watcher into the subs array. The subs in the DEP corresponding to this key are now subscribed to watcher.

6. Return to the get function

  • Getter executes popTarget, which deletes the last bit in the targetStack queue and assigns dep. target to the last bit in the deleted targetStack queue. For what purpose? Restore the dep. target to the previous state, because the current component’s data dependency collection has been completed, and the corresponding render DEP. target needs to be changed to the previous component (this operation is discussed later on in computed source code).

  • Finally, the cleanupDeps function is called, whose main purpose is to prevent the following scenario: V-if renders different subtemplates A and B. When rendering A meets certain conditions, the data in A will be accessed. At this time, getter is added for the data used by A, and dependent collection is performed. If template B is rendered using a modified condition, getter will be added to the data used by TEMPLATE B. If there is no dependency removal process, then modifying template A data will notify a data subscription callback, which is obviously wasteful. So what he did was an optimization, basically deleting the redundant dependencies the next time he added them to avoid waste.