preface

As one of the three front-end frameworks, VUE is far more popular than other frameworks in China, especially the release of VUE3 version, which pushes the development of VUE to a higher level. Let’s take “Vue design and Implementation” as the textbook to peek into the design principles and implementation details of Vue.

Not big guy, if the article has the error welcome below message, study together.

Example code: Github

Side effects

I first heard of this concept in React Hook. In fact, it’s easy to understand that mapping in the real world is when you do something that affects other people.

So for a function to do something that has an intersection with the outside of the function (fetching or modifying the outside variable), it has a side effect and becomes a side effect function.

var a = 1;
function fn(){
   a = 2
}
Copy the code

So if you classify functions this way you can divide them into pure functions and side effects.

So what does that have to do with the response that we’re going to learn?

Responsive prototype

Think about it. How would you describe responsiveness in vernacular? I think it looks something like this: I changed a variable and the page rerendered itself.

We can also think of it as, I modified a variable and its render function was executed at the same time so that the page was rendered again, and the render function that was executed at that time is what we call the side effect function, and it has a strong dependency on that variable.


let name = 'the nuggets'

function render(){
    document.body.innerHtml = name
}

// Change the variable
name = 'Hello, Nuggets.';
// Execute the render function
render();

Copy the code

The above code is not automatic enough. You have to manually execute the rendering function every time a variable changes. Is there a way to automate this process a bit more? Let’s use Proxy to transform our code.

let data = {
    name: 'the nuggets'};const obj = new Proxy(data, {
get(target, key) {
  return target[key];
},
set(target, key, val){ target[key] = val; render(); }});function render() {
    document.body.innerHTML = obj.name;
}
// First render
render();
// Change nam after 2 seconds
setTimeout(() = > {
    obj.name = 'Hello, Nuggets.';
}, 2000);
Copy the code

The effect is as follows:

It looks like we’ve implemented a simple responsive prototype, but we need to think, is the side effect of Vue only performing the render function to render the page? Obviously not. When a function internally depends on a reactive variable, if the reactive variable changes, the function needs to be reexecuted.

So the above summary of responsiveness is obviously incomplete. It should be: I modify a variable, and the function that depends on that variable is reexecuted. This function may be a rendering function, but it may be another function.

So our code should look something like this

// Define a side effect function that will be executed automatically if obj.name changes
effect(() = > {
  document.body.innerHTML = obj.name;
});

 setTimeout(() = > {
    obj.name = 'Hello, Nuggets.';
 }, 2000);
Copy the code

From a practical point of view, effect is a higher-order function because it receives a function fn. The fn function needs to be executed when obj fires the set, so the fn function needs to be exposed for use.

 let activeEffect;
 function effect(fn) {
    activeEffect = fn;
    fn();
 }
Copy the code

Of course, there is more than one side effect function, so we need an array to store all the side effects.

// Store all side effects
let effects = new Set(a);Copy the code

Now we need to think about, when do we need to collect these side effects? When to perform these side effects? Collect when get is triggered and execute when set is triggered, so the complete code looks like this:

  let activeEffect;
  let effects = new Set(a);let data = {
    name: 'the nuggets'};const obj = new Proxy(data, {
    get(target, key) {
      if (activeEffect) {
      // Collect side effects
        effects.add(activeEffect);
      }
      return target[key];
    },
    set(target, key, val) {
      target[key] = val;
      // Perform all side effects
      effects.forEach((effect) = >effect()); }});function effect(fn) {
    activeEffect = fn;
    fn();
  }
  // Effect is expected to be executed again when obj.name changes
  effect(() = > {
    document.body.innerHTML = obj.name;
  });
// There can be multiple effects
  effect(() = > {
    console.log(obj.name);
  });
  setTimeout(() = > {
    obj.name = 'Hello, Nuggets.';
  }, 2000);
Copy the code

This collection and trigger dependent model is also known as the publish and subscribe model.

Make the response more accurate

So let’s execute this code

  let activeEffect;
  let effects = new Set(a);let data = {
    name: 'the nuggets'.age:10};const obj = new Proxy(data, {
    get(target, key) {
      if (activeEffect) {
        effects.add(activeEffect);
      }
      return target[key];
    },
    set(target, key, val) {
      target[key] = val;
      effects.forEach((effect) = >effect()); }});function effect(fn) {
    activeEffect = fn;
    fn();
  }
  // Effect is expected to be executed again when obj.name changes
  effect(() = > {
    document.body.innerHTML = obj.name;
    console.log(obj.name);
  });

  setTimeout(() = > {
  // Note that age is changed instead of name
    obj.age = 18;
  }, 2000);
Copy the code

We found that effect reexecuted when we changed obj.age, even though there was no dependency on it.

This is because our responsive system does not have enough granularity of dependency collection and trigger, and our current solution is that every change in the value of obJ triggers the update of side effects, which is clearly not correct.

Therefore, when collecting dependencies, it must be accurate to obJ’s key, and the general data structure design is as follows:

In VUe3, WeakMap is used to describe this relationship

WeakMap, Map, Set please make up.

The code is shown below, with comments in key areas.

  let activeEffect;
  let effects = new WeakMap(a);// Store all objects and side effects
  let data = {
    name: 'the nuggets'};const obj = new Proxy(data, {
    get(target, key) {
       // Determine if there is a target tree
      let depsMap = effects.get(target);
      // If not, create it with current obj as key
      if(! depsMap) { effects.set(target, (depsMap =new Map()));
      }
      // see if obj. XXX creates dependencies for specific keys
      let deps = depsMap.get(key);
      // If not, create it
      if(! deps) { depsMap.set(key, (deps =new Set()));
      }
      // If there are dependencies, add them to the corresponding key
      if (activeEffect) {
        deps.add(activeEffect);
      }
      return target[key];
    },
    set(target, key, val) {
      target[key] = val;
      // Retrieve the corresponding dependency from WeakMap
      const depsMap = effects.get(target);
      if (depsMap) {
      // Retrieve the key corresponding to obj
        const effect = depsMap.get(key);
        // Execute all side effects if there are any
        effect && effect.forEach((fn) = >fn()); }}});function effect(fn) {
    activeEffect = fn;
    fn();
  }
  effect(() = > {
    document.body.innerHTML = obj.name;
    console.log(obj.name);
  });
  setTimeout(() = > {
    obj.age = 18;
  }, 2000);
Copy the code

The corresponding data structure is as follows:

The beauty of Vue3’s responsive design lies in this, where the entire collection of responsive dependencies and corresponding relationships are clearly described through such a data structure. Of course, what we have realized today is the core of vuE3 responsiveness, but not all of it. A complete responsive system will be very complex and need to take into account many situations, but ultimately it will be patched up based on the above data structure.

The last

  • Are side effects nested in an endless loop?
  • What if there is judgment logic in the side effect function?
  • Vue3watchHow is the function implemented?
  • .

Let’s explore!

If you can help, please like and follow 😘