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?
Vue3
的watch
How is the function implemented?- .
Let’s explore!
If you can help, please like and follow 😘