preface

This article mainly handwritten Vue2.0 source code – rendering update principle

In the last article, we mainly introduced Vue’s initial rendering principle. We finished the process of mapping the data to the view layer, but when we changed the data, we found that the page did not automatically update. One of the features of Vue is data driven This article mainly adopts the observer mode to define Watcher and Dep to accomplish the dependency collection and distribution of updates so as to realize the rendering update

Applicable to the crowd: no time to look at the official source code or look at the source code to see the more meng and do not want to see the students

Tip: this is a little more difficult is the whole Vue source code is very core content of the subsequent computing properties and custom watcher and $set $DELETE Api implementation need to understand this idea small make up see the source code is also seen several times to understand hope you overcome the difficulties together to achieve it!


The body of the

    <script>
      // Instantiate Vue
      let vm = new Vue({
        el: "#app".data() {
          return {
            a: 123}; },// render(h) {
        // return h('div',{id:'a'},'hello')
        // },
        template: `<div id="a">hello {{a}}</div>`});// We simulate the update here
      setTimeout(() = > {
        vm.a = 456;
        // This method is the core of refreshing the view
        vm._update(vm._render());
      }, 1000);
    </script>
Copy the code

We call vm._update(vm._render()) in setTimeout to update the view, because we know that this method is the core of rendering, but we can’t ask the user to call the render method to update the view every time the data changes We need a mechanism to automatically update the data as it changes

1. Define the Watcher

// src/observer/watcher.js

// The global variable id is increable every time new Watcher is added
let id = 0;

export default class Watcher {
  constructor(vm, exprOrFn, cb, options) {
    this.vm = vm;
    this.exprOrFn = exprOrFn;
    this.cb = cb; Callback functions such as watcher can execute beforeUpdate methods before watcher updates
    this.options = options; // The additional option true means render watcher
    this.id = id++; // Unique identifier of watcher
    // If the expression is a function
    if (typeof exprOrFn === "function") {
      this.getter = exprOrFn;
    }
    // The instantiation will call the get method by default
    this.get();
  }
  get() {
    this.getter(); }}Copy the code

In the Observer folder, create a new “watcher.js” that represents “observer”. This first introduces the “observer” pattern used in Vue. We can think of watcher as an observer and it needs to subscribe to changes in data and notify it to perform certain methods when the data changes It’s essentially a constructor that initializes and executes the get method

2. Create the render Watcher

// src/lifecycle.js
export function mountComponent(vm, el) {
  // The _update and._render methods are mounted on the Vue prototype similar to the _init methods

  // Introduce the concept of watcher here to register a render watcher to perform the vm._update(vm._render()) method to render the view

  let updateComponent = () = > {
    console.log("Refresh page");
    vm._update(vm._render());
  };
  new Watcher(vm, updateComponent, null.true);
}
Copy the code

We define a render Watcher in the component mount method whose main function is to perform the core render page method

3. Define the Dep

// src/observer/dep.js

// Dep and watcher have a many-to-many relationship

// Each attribute has its own DEP

let id = 0; // The unique identifier of the dep instance
export default class Dep {
  constructor() {
    this.id = id++;
    this.subs = []; // This is the container where the watcher is stored}}Dep.target is null by default
Dep.target = null;
Copy the code

Dep is also a constructor that can be thought of as the observed in the observer mode collecting watcher in subs and notifying subs of all watcher updates when the data changes

Dep. Target is a global Watcher pointing to an initial state of NULL

4. Object dependency collection

// src/observer/index.js

// Object.defineProperty Data hijack core compatibility in IE9 and above
function defineReactive(data, key, value) {
  observe(value);

  let dep = new Dep(); // Instantiate one Dep for each attribute

  Object.defineProperty(data, key, {
    get() {
      // We can collect the watcher in the DEP when the page is valued
      if (Dep.target) {
        // If there is a watcher, dep will save the watcher and watcher will save the dep
        dep.depend();
      }
      return value;
    },
    set(newValue) {
      if (newValue === value) return;
      // If the new value assigned is also an object to observe
      observe(newValue);
      value = newValue;
      dep.notify(); // Tell the render watcher to update -- dispatch the update}}); }Copy the code

The appeal code relies on collecting and distributing updates by placing our defined render Watcher in the DEP’s subs array when data is accessed and placing deP instance objects in the render Watcher to notify DEP when data is updated Watcher updates stored by subs

5. Improve the watcher

// src/observer/watcher.js

import { pushTarget, popTarget } from "./dep";

// The global variable id is increable every time new Watcher is added
let id = 0;

export default class Watcher {
  constructor(vm, exprOrFn, cb, options) {
    this.vm = vm;
    this.exprOrFn = exprOrFn;
    this.cb = cb; Callback functions such as watcher can execute beforeUpdate methods before watcher updates
    this.options = options; // The additional option true means render watcher
    this.id = id++; // Unique identifier of watcher
    this.deps = []; // The container where dep is stored
    this.depsId = new Set(a);// Used to remove dep
    // If the expression is a function
    if (typeof exprOrFn === "function") {
      this.getter = exprOrFn;
    }
    // The instantiation will call the get method by default
    this.get();
  }
  get() {
    pushTarget(this); // Push the current watcher instance to the global dep.target before calling the method
    this.getter(); // If watcher is a render watcher then it is equivalent to executing vm._update(vm._render()). This method is valued when render is executed to collect dependencies
    popTarget(); // Remove the current watcher instance from the global dep.target after calling the method
  }
  // Put the dep in the deps and make sure that the same deP is only saved to watcher once and the same watcher is only saved to dep once
  addDep(dep) {
    let id = dep.id;
    if (!this.depsId.has(id)) {
      this.depsId.add(id);
      this.deps.push(dep);
      // Add the watcher instance to dep's subs container by calling dep's addSub method
      dep.addSub(this); }}// We simply execute the following get method and then we need to calculate the properties
  update() {
    this.get(); }}Copy the code

Watcher assigns itself to dep. target before and after calling the getter method to make it easier to rely on collecting the update method for updates

6. Improve the dep

// src/observer/dep.js

// Dep and watcher have a many-to-many relationship
// Each attribute has its own DEP
let id = 0; // The unique identifier of the dep instance
export default class Dep {
  constructor() {
    this.id = id++;
    this.subs = []; // This is the container where the watcher is stored
  }
  depend() {
    // If a watcher currently exists
    if (Dep.target) {
      Dep.target.addDep(this); // Store its own -dep instance in watcher}}notify() {
    // Execute the watcher update method in subs
    this.subs.forEach((watcher) = > watcher.update());
  }
  addSub(watcher) {
    // Add watcher to its subs container
    this.subs.push(watcher); }}Dep.target is null by default
Dep.target = null;
// The stack structure is used to store watcher
const targetStack = [];

export function pushTarget(watcher) {
  targetStack.push(watcher);
  Dep.target = watcher; // dep. target points to the current watcher
}
export function popTarget() {
  targetStack.pop(); // The current watcher goes off the stack and gets the previous watcher
  Dep.target = targetStack[targetStack.length - 1];
}
Copy the code

Define the methods that collect dependencies and place them in watcher’s deps container

Thinking about? If the array is like {a:[1,2,3]}, a.ush (4) does not trigger an automatic update because our array does not collect dependencies

7. Array dependency collection

// src/observer/index.js

// Object.defineProperty Data hijack core compatibility in IE9 and above
function defineReactive(data, key, value) {
  let childOb = observe(value); ChildOb is the Observer instance

  let dep = new Dep(); // Instantiate one Dep for each attribute

  Object.defineProperty(data, key, {
    get() {
      // We can collect the watcher in the DEP when the page is valued
      if (Dep.target) {
        // If there is a watcher, dep will save the watcher and watcher will save the dep
        dep.depend();
        if (childOb) {
          // The value of the attribute is still an object containing an array and an object. ChildOb refers to the deP in the Observer instance object for dependency collection
          // for example {a:[1,2,3]} {a:[1,2,3]} the value of the attribute a is an array
          childOb.dep.depend();
          if (Array.isArray(value)) {
            // if the data structure is similar to {a:[1,2,[3,4,[5,6]]}, then we only rely on the array at the first level when we access a But if we change the value of the second array in a, we need to update the page so we need to do a dependency collection on the array recursively
            if (Array.isArray(value)) {
              // If the inside is still an array
              dependArray(value); // Keep collecting dependencies}}}}return value;
    },
    set(newValue) {
      if (newValue === value) return;
      // If the new value assigned is also an object to observe
      observe(newValue);
      value = newValue;
      dep.notify(); // Tell the render watcher to update -- dispatch the update}}); }// Collect array dependencies recursively
function dependArray(value) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i];
    __ob__ means that e has been observed responsively but has not collected dependencies so collect them into the DEP of its own Observer instance
    e && e.__ob__ && e.__ob__.dep.depend();
    if (Array.isArray(e)) {
      // Collect dependencies recursively if there are arrays in the arraydependArray(e); }}}Copy the code

If the value of the object property is an array then doing childob-dep. Depend () to collect the array’s dependencies if the array has an array in it then you need to recursively iterate through the collection because the dependencies are only collected when the access to the data triggers get and it starts with just recursively responding to the data and not collecting the dependencies These two points need to be distinguished

8. Dispatch updates to arrays

// src/observer/array.js

methodsToPatch.forEach((method) = > {
  arrayMethods[method] = function (. args) {
    // The results of the prototype method are retained
    const result = arrayProto[method].apply(this, args);
    // This sentence is the key
    {a:[1,2,3]} {a:[1,2,3]} {a:[1,2,3]} {a:[2,3]}} {a:[2,3]}
    const ob = this.__ob__;
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
      default:
        break;
    }
    if (inserted) ob.observeArray(inserted); // Observe each new item
    ob.dep.notify(); Ob refers to the Observer instance corresponding to the array. When we get, we determine that if the attribute value is still an object, then we collect dependencies in the DEP of the Observer instance
    return result;
  };
});
Copy the code

The key code is ob.dep.notify()

Render an updated mind map

summary

Here is a picture of a whole Vue reactive principle From the data we took – > template parsing — — > template rendering view > data change automatically update the process has written again Especially the introduction of render update relevant knowledge Recommended inculcate principle for themselves after it again Because the Vue many core principles and The apis are all related to this

At this point, Vue’s rendering update principle has been completed. Please leave a comment if you don’t understand or have a dispute

Finally, if you find this article helpful, remember to like it three times. Thank you very much!

Series of links (will be updated later)

  • Handwriting Vue2.0 source code (a) – response data principle
  • Handwriting Vue2.0 source code (2) – template compilation principle
  • Handwriting Vue2.0 source code (three) – initial rendering principle
  • Handwriting Vue2.0 source code (four) – rendering update principle
  • Handwriting Vue2.0 source code (five) – asynchronous update principle
  • Handwriting Vue2.0 source code (six) -diff algorithm principle
  • Handwriting Vue2.0 source code (seven) -Mixin Mixin principle
  • Handwriting Vue2.0 source code (eight) – component principle
  • Handwriting Vue2.0 source code (nine) – listening attribute principle
  • Handwriting Vue2.0 source code (ten) – the principle of computing attributes
  • Handwriting Vue2.0 source code (eleven) – global API principle
  • The most complete Vue interview questions + detailed answers
  • Handwritten vue-Router source code
  • Write vuex source code
  • Handwriting vue3.0 source code

Shark brother front touch fish technology group

Welcome technical exchanges within the fish can be pushed for help – link