The responsive principle of VUE2.0
This article is a long one. If you are familiar with the responsivity principle of vue2.0, you can skip this part
In VUE, we use the most reactive data, which brings us a lot of convenience. Speaking of reactive data, that is, when data changes, it is necessary to update the view. Let’s take a step by step look at how this function is implemented in VUe2.0.
let data = {name: "yangbo"}
Copy the code
Then we change this data:
data.name = 'yb'
Copy the code
We all know that in vUE data changes and we need to update the view, so we need a way to update the view:
function updateView(){
console.log('Triggered update view method')}Copy the code
Now the question is, how do we trigger our updateView() method when we set the property value of this object, Here we use the Object.defineProperty() method, which we use to redefine properties and add getters and setters to objects’ properties. Let’s start with the first step:
We define a method to observe the data in which we call the defineProperty method to set getters and setters for the properties of the data. DefineProperty () : object.defineProperty () : object.defineProperty () : object.defineProperty () : object.defineProperty () : object.defineProperty () : object.defineProperty () : Object.defineProperty() : Object.defineProperty() : Object.defineProperty() : Object.defineProperty() : Get () : value (); In the set method we call the update view method and assign the new value to value
function watchData(target) {
if(typeof target ! = ='object' || target === null) {
return target;
}
for (let key in target) {
defineProperty(target, key, target[key])
}
}
functionDefineProperty (target, key, value) {// Define a new key for target and add getter and setter object.defineProperty (target, key, value) {get() {
return value
},
set(newVal) {
if(newVal ! == value) { updateView(); value = newVal; }}}};Copy the code
The above code completes the first step, then the problem arises again, our data cannot be all one layer, may be multiple layers of data, such as:
let data = {
name: 'yangbo',
info: {
age: '26'}}Copy the code
So, what we need to do is if the inner layer is still an object and we need to look at the object again, we add a line of code to the defineProperty function, which actually creates recursion:
functionDefineProperty (target, key, value) {// Add a line of watchData(value); // Add getter and setter object.defineProperty (target, key, {get() {
return value
},
set(newVal) {
if(newVal ! == value) { updateView(); value = newVal; }}}};Copy the code
So far we have implemented responsiveness for data. As you can see from the above code, recursion can affect performance if your Object level is deep. Let’s consider that if we assign an object as follows, it will trigger several methods to update the view:
data.info = {sex: 'man'}
data.info.sex = 'woman'
Copy the code
Only data.info = {sex: ‘man’} will trigger the update view method. Because we did not define getters and setters for sex in the above method, we need to add another line to defineProperty as follows:
functiondefineProperty(target, key, value) { watchData(value); // Add getter and setter object.defineProperty (target, key, {get() {
return value
},
set(newVal) {
if(newVal ! == value) {// Add a new line of code, if set to Object, add getter and setter watchData(newVal) for the new value as well; updateView(); value = newVal; }}}};Copy the code
So now we’re going to update the view twice, and we’re basically getting our object responsive; But further down the line, the question remains: If attributes don’t exist, will the new attributes be responsive? What if the value of the property is an array? Let’s take a look. With current code, the following changes to the array in the data do not trigger the update view method:
let data = {name: 'yangbo', phone: [1, 2, 3]}; watchData(data); data.phone.push(4);Copy the code
Then let’s make this way change data will also trigger the update view, first of all we see, we call the push method, this method is an Array, so we need to rewrite Array method, here we cannot directly to rewrite Array method on the prototype, and we are getting all methods of an Array and to rewrite some of the methods, In this example, we’ll just rewrite the push and pop methods. We need to create an Object that is the same as array. property.
letArrayProperty = Array.prototype; // Array prototypeletproto = Object.create(ArrayProperty); / / inheritanceCopy the code
Then we redefine the methods on proto. Remember, when we define the methods on propO, we also need to use the methods on ArrayProperty, so we’ll call the original array method, but before we call the update view method, Don’t forget to use the call method to change the direction of this. What we’re doing here is hijacking the function:
['push'.'pop'].forEach((method) => {
proto[method] = function() { updateView(); ArrayProperty[method]. Call (this,... arguments); }});Copy the code
We’ve hijacked the watchData method, so how do we get target to find the proto method? Let’s check if target is an array. If it is an array, let’s make its __proto__ point to proto:
function watchData(target) {
if(typeof target ! = ='object' || target === null) {
return target;
}
if(array.isarray (target)) {// If it is an Array, make the chain of target point to proto target.__proto__ = proto; }for (let key in target) {
defineProperty(target, key, target[key])
}
}
Copy the code
That’s it for responding to objects and arrays. Here’s the complete code:
letArrayProperty = Array.prototype; // Array prototypeletproto = Object.create(ArrayProperty); / / inheritance ['push'.'pop'].forEach((method) => {
proto[method] = function() { updateView(); ArrayProperty[method]. Call (this,... arguments); }})function updateView(val) {
console.log('Triggered update view method');
}
function watchData(target) {
if(typeof target ! = ='object' || target === null) {
return target;
}
if(array.isarray (target)) {// If it is an Array, make the chain of target point to proto target.__proto__ = proto; }for (let key in target) {
defineProperty(target, key, target[key])
}
}
functiondefineProperty(target, key, value) { watchData(value); // Add getter and setter object.defineProperty (target, key, {get() {
return value
},
set(newVal) {
if(newVal ! == value) { watchData(newVal); updateView(); value = newVal; }}}};let data = {name: 'yangbo', phone: [1, 2, 3]}; watchData(data); data.phone.push(4); console.log(data.phone); //let data = {name: 'yangbo', info: {age: 25}}
// watchData(data);
// data.name = 'yb';
// data.info = {sex: 'man'}
// data.info.sex = 'woman'
Copy the code
The responsive principle of VUE3.0
We have already written a simple version of vue2.0 responsivity, we can find that in 2.0, the default is to recurse data, and the object does not exist attributes can not be intercepted, with these problems we take a look at vue3.0 responsivity implementation principle ~
Here’s how we’re experiencing Vue3.0:
- Clone the vue3.0 source code locally
git clone [email protected]:vuejs/vue-next.git
Copy the code
- Install the dependencies, and then execute them in the root directory
npm run dev
Copy the code
- Is compiled vue3.0 package/vue/dist/vue. Global. Js, the vue. Global. Js to introduction you can use the demo, before that, be sure to look at the vue Composition API
Next, let’s use Vue3.0
<! DOCTYPE html> <html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./vue.global.js"></script>
<script>
let proxy = Vue.reactive({name: 'yangbo'});
proxy.name = 'yb';
</script>
</body>
</html>
Copy the code
Reactive data in vue3.0 is implemented by Proxy in ES6. Vue.reactive() returns a Proxy, and let Proxy = vue.reactive ({name: ‘yangbo’}); Should trigger an update to the view, and when the value proxy.name = ‘yb’; Is triggered to update the view again. What does that do in Vue3.0? The main logic of VUe3.0 is realized by vue.effect (), which will be executed once first and then when the data changes. Next, we will implement it step by step. This time, we will use JS instead of TS to realize the idea.
- Vue. Reactive is implemented in Vue3.0. Reactive is implemented in Vue3.0. Before that still need to be familiar with the Proxy ES6 ~ Vue. Reactive type returns a response object, so we in the reactive method returns a response object type:
function reactive(target) {
return createReactive(target);
}
functionCreateReactive (target) {// Create a responsive objectif(! (typeof target ==='object'&& target ! == null)) {returntarget; } // Create an observerletBaseHandle = {get(target, key, receiver) {// Obtainlet datas = Reflect.get(target, key, receiver);
return datas;
},
set(Target, key, value, receiver) {// Setlet res = Reflect.set(target, key, value, receiver);
console.log(res);
returnres; }}let observed = new Proxy(target, baseHandle);
return observed;
}
let proxy = reactive({name: 'yangbo'})
proxy.name = 'yb';
console.log(proxy.name);
Copy the code
Reflect. Get (target, key, receiver) and target[key] are equivalent in TERMS of get. In the set method of our code, we also use reflect.set () to set the value, and it will return a Boolean value to indicate whether the value was set successfully. So when we Reflect, in the get method, we decide if the value we’re getting is Object or not, and if Reflect results in Object, we just proxy the result.
Get (target, key, receiver) {// Getletdatas = Reflect.get(target, key, receiver); // Change the following line of codereturn typeof target === 'object'&& target ! == null ? reactive(datas) : datas; }Copy the code
Compare that to 2.0, which is recursive from the start, and 3.0, which is recursive only when necessary. Above we implement the proxy of Object, but we also need to prevent the same Object from being proxy many times, so here we use new WeakMap() weak reference we define two WeakMap to judge whether the current Object is proxy:
let sourceProxy = new WeakMap(); // Store the original object and the proxied objectsourceProxy * {* original object: the object being proxied *} ** */lettoRaw = new WeakMap(); /** * toRaw * {** toRaw * {** toRaw *} ** */Copy the code
I then changed the createReactive method to the following:
functionCreateReactive (target) {// Create a responsive objectif(! (typeof target ==='object'&& target ! == null)) {returntarget; } // Determine whether target is proxiedif(sourceProxy.get(target)) {
return sourceProxy.get(target); } // Check whether there is a target in the propped object to prevent an object from being propped more than onceif (toRaw.has(target)) {
returntarget; } // Create an observerletBaseHandle = {get(target, key, receiver) {// Obtainletdatas = Reflect.get(target, key, receiver); // Change the following line of codereturn typeof target === 'object'&& target ! == null ? reactive(datas) : datas; },set(Target, key, value, receiver) {// Setlet res = Reflect.set(target, key, value, receiver);
console.log(res);
returnres; }}letobserved = new Proxy(target, baseHandle); / / set weakmapsourceProxy.set(target, observed);
toRaw.set(observed, target);
return observed;
}
Copy the code
Next we implement dependency collection (publish subscribe), remember the effect method we talked about earlier? It is executed once by default and again when dependent on data changes. Now we implement this effect, which is the core of responsiveness; First our effect method takes a function, and we make it reactive and make it run once by default:
function effect(fn) {
leteffect = createReactiveFn(fn); // create effect(); // Execute once by default}Copy the code
Next we write the createReactiveFn method:
let stacks = [];
function createReactiveFn(fn) {
let eFn = function() {// execute fn and stack fnreturn carryOut(effect, fn)
}
return eFn;
}
functionIf (effect, fn) {// execute fn and stack fn. fn(); }Copy the code
When taking a value, that is, in the get method, we need to map the value and effect. When the value changes, we directly execute the effect corresponding to the value. Next, we first implement this part of the method to create the association, and finally we need a data structure as follows:
{
target: {
key: [fn, fn]
}
}
Copy the code
We write the subscribeTo to create the association:
let targetMap = new WeakMap();
function subscribeTo(target, key) {
let effect = status[status.length - 1];
if(effect) {// If so, the association is createdlet maps = targetMap.get(target);
if(! maps) { targetMap.set(target, maps = new Map()); }let deps = maps.get(key);
if(! deps) { deps.set(key, deps = new Set()); }if(! deps.has(effect)) { deps.add(effect); }}}Copy the code
Remember stacks. Push (effect); After we have done the correlation, this effect is actually meaningless on the stack, so we remove it from the stack:
functionIf (effect, fn) {// execute fn and stack fn. fn(); status.pop(effect); }Copy the code
Now that we have created the association, when updating the value, in the set method we should fetch effect from targetMap:
function trigger(target, type, key) {
let tMap = targetMap.get(target);
if (tMap) {
letkeyMaps = tMap.get(key); // Execute effect corresponding to keyif(keyMaps) { keyMaps.forEach((eff) => { eff(); })}}}Copy the code
The complete code for implementing this article is posted below:
let sourceProxy = new WeakMap(); // Store the original object and the proxied objectlettoRaw = new WeakMap(); // Store the proxied object and the original objectfunction reactive(target) {
return createReactive(target);
}
functionCreateReactive (target) {// Create a responsive objectif(! (typeof target ==='object'&& target ! == null)) {returntarget; } // Determine whether target is proxiedif(sourceProxy.get(target)) {
return sourceProxy.get(target); } // Check whether there is a target in the propped object to prevent an object from being propped more than onceif (toRaw.has(target)) {
returntarget; } // Create an observerletBaseHandle = {get(target, key, receiver) {// Obtainletdatas = Reflect.get(target, key, receiver); // subscribeTo(target, key); // Re-execute effect when key changes // Change the following line of codereturn typeof target === 'object'&& target ! == null ? reactive(datas) : datas; },set(Target, key, value, receiver) {// Setlet oldValue = target[key];
letres = Reflect.set(target, key, value, receiver); // filter meaningless changesif(! Target.hasownproperty (key)) {// Add property console.log('new');
trigger(target, 'add', key);
} else if(oldValue ! == value) {// Modify attribute console.log('change');
trigger(target, 'set', key);
}
console.log(res);
returnres; }}letobserved = new Proxy(target, baseHandle); / / set weakmapsourceProxy.set(target, observed);
toRaw.set(observed, target);
return observed;
}
let stacks = [];
lettargetMap = new WeakMap(); / / response typefunction effect(fn) {
leteffect = createReactiveFn(fn); // create effect(); // Execute once by default}function createReactiveFn(fn) {
let eFn = function() {// execute fn and stack fnreturn carryOut(eFn, fn)
}
return eFn;
}
function* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * fn(); } finally { stacks.pop(effect); }}function subscribeTo(target, key) {
let effect = stacks[stacks.length - 1];
if(effect) {// If so, the association is createdlet maps = targetMap.get(target);
if(! maps) { targetMap.set(target, maps = new Map); }let deps = maps.get(key);
if(! deps) { maps.set(key, deps = new Set()); }if(! deps.has(effect)) { deps.add(effect); }}}function trigger(target, type, key) {
let tMap = targetMap.get(target);
if (tMap) {
letkeyMaps = tMap.get(key); // Execute effect corresponding to keyif(keyMaps) { keyMaps.forEach((eff) => { eff(); })}}} //let proxy = reactive({name: 'yangbo'})
// proxy.name = 'yb';
// console.log(proxy.name);
let proxyObj = reactive({name: 'yangbo'});
effect(() => {
console.log(proxyObj.name);
})
proxyObj.name = 'yb'
Copy the code
The above simple imitation vue3.0 on the implementation of responsive data, using JS implementation, interested can see vue3.0 source TypeScript writing and implementation, this article is for reference only, this article’s implementation code please go to gitHub, welcome you to guide communication!