This is the 23rd day of my participation in Gwen Challenge

One, foreword

In the previous chapter, the relationship between DEP and Watcher in the process of dependency collection was mainly introduced:

Dep. Target is used to record the Watcher instance before the get method in the Watcher class is about to trigger a view update. In object.defineProperty's get method, deP is associated with watcher by having the deP of the data remember the render Watcher, and the view will only be updated if the data involved in the view rendering changesCopy the code

This article continues to rely on the collected view update section


Second, implement view update logic

1. Check weight watcher

Question: What happens if the same data is used more than once in a view?

According to current logic, when the same data is used multiple times in a view, the same Watcher is saved multiple times in the DEP

<div id="app">
  <li>{{name}}</li>
  <li>{{name}}</li>
  <li>{{name}}</li>
</div>
Copy the code

In the deP of name, three of the same render Watcher will be saved

So watcher needs to do a replay check

Add an ID to the Watcher instance. Each time the new Watcher id increases, the new Watcher id will be used as a flag to check the Watcher instanceCopy the code
let id = 0;
class Watcher {
  constructor(vm, fn, cb, options){
    this.vm = vm;
    this.fn = fn;
    this.cb = cb;
    this.options = options;
    this.id = id++;   // Watcher unique tag

    this.getter = fn;
    this.get();
  }
  get(){
    Dep.target = this;
    this.getter();
    Dep.target = null; }}export default Watcher
Copy the code

2. Make Watcher remember deP, too

Similarly, watcher needs to remember the DEP as well

let id = 0;

class Dep {
  constructor(){
    this.id = id++;
    this.subs = [];
  }
  // Make Watcher remember deP and deP remember Watcher
  depend(){
    AddDep: makes the current watcher remember the dep
    Dep.target.addDep(this);  
  }
  // let deP remember watcher - be called in watcher
  addSub(watcher){
    this.subs.push(watcher);
  }
}

Dep.target = null;  // Static property to record the current watcher

export default Dep;
Copy the code

Why is it implemented this way?

If you want to remember each other, in Watcher you need to do a duplicate check on deP. The deP also does a duplicate check on watcher; After associating deP with Watcher in this way, you only need to judge onceCopy the code
import Dep from "./dep";
let id = 0;
class Watcher {
  constructor(vm, fn, cb, options){
    this.vm = vm;
    this.fn = fn;
    this.cb = cb;
    this.options = options;
    
    this.id = id++;
    this.depsId = new Set(a);// The unique ID used to save the deP instance for the current watcher
    this.deps = []; // For the current watcher to save the DEP instance
    this.getter = fn;
    this.get();
  }
  addDep(dep){
    let did = dep.id;
    / / dep to check again
    if(!this.depsId.has(did)){
      // Make Watcher remember deP
      this.depsId.add(did);
      this.deps.push(dep);
      // Let deP remember Watcher
      dep.addSub(this); }}get(){
    Dep.target = this;
    this.getter();
    Dep.target = null; }}export default Watcher;
Copy the code

This implementation keeps DEP and Watcher in a relative relationship:

If you have deP in Watcher; So there must be watcher in deP; If there is no DEP in Watcher; There must be no Watcher in deP; Therefore, deP and Watcher can be checked only once.Copy the code

3. Data changes, triggering view updates

The set method that goes into Object.defineProperty when the view is updated needs to inform all watcher collections in the DEP to execute the view update method in the set methodCopy the code
// src/observe/index.js#defineReactive

function defineReactive(obj, key, value) {
  observe(value);
  let dep = new Dep();  // Add a deP for each attribute
  Object.defineProperty(obj, key, {
    get() {
      if(Dep.target){
        dep.depend();
      }
      return value;
    },
    set(newValue) {
      if (newValue === value) return
      observe(newValue);
      value = newValue;
      dep.notify(); // Notify all watcher collected in the current DEP to perform view updates in turn}})}Copy the code

4. Add notify to Dep:

let id = 0;

class Dep {
  constructor(){
    this.id = id++;
    this.subs = [];
  }
  depend(){
    Dep.target.addDep(this);  
  }
  addSub(watcher){
    this.subs.push(watcher);
  }
  // All the watcher collected in deP executes the update method update in sequence
  notify(){
    this.subs.forEach(watcher= > watcher.update())
  }
}
Dep.target = null;
export default Dep;
Copy the code

5. Add update method to Watcher

import Dep from "./dep";
let id = 0;
class Watcher {
  constructor(vm, fn, cb, options){
    this.vm = vm;
    this.fn = fn;
    this.cb = cb;
    this.options = options;

    this.id = id++;
    this.depsId = new Set(a);this.deps = [];
    this.getter = fn;
    this.get();
  }
  addDep(dep){
    let did = dep.id;
    if(!this.depsId.has(did)){
      this.depsId.add(did);
      this.deps.push(dep);
      dep.addSub(this); }}get(){
    Dep.target = this;
    this.getter();
    Dep.target = null;
  }
  // Execute the view rendering logic
  update(){
    this.get(); }}export default Watcher;
Copy the code

Problem of 6.

Frequently updating the same data multiple times causes the view to be re-rendered frequently

let vm = new Vue({
  el: '#app'.data() {
    return { name: "Brave" , age: 123}}}); vm.name ="Brave Wang";
vm.name = "Brave";
vm.name = "Brave Wang";
vm.name = "Brave";
vm.name = "Brave Wang";
vm.name = "Brave";
Copy the code
The value of name changes 6 times, but in the end there is no change. Brave is still Brave, so we need to change to the mechanism of asynchronous updateCopy the code

Three, the end

This article introduces the view update part of Vue dependency collection, mainly covering the following points:

View initialization:

  • The render method takes the value and goes to the Get method of Object.defineProperty
  • The get method adds the DEP to the data and records the current render watcher
  • Record mode: Watcher checks the replay and remembers deP, and DEP remembers Watcher

When data is updated:

  • When the data is changed, the set method of Object.defineProperty is entered
  • In the set method, make all the watcher collected in the DEP perform the view render operation watcher.get()
  • The current render watcher is recorded through dep.target before the view renders (before the this.getter method executes)
  • Repeat the view initialization process

Next: Vue asynchronous update


Maintenance logs:

20210802: Modify the abstract