Responsiveness of VUE
When I first started using VUE for project development, responsive data rendering was one of the features that surprised me the most. Let’s look at this code:
<body>
<div id="app">
<div>Modify the quantity of goods:<input type="number" v-model="product.quantity">
</div>
<div>Modify the price of goods:<input type="number" v-model="product.price">
</div>
<p>Total price: {{total}}</p>
</div>
</body>
<script src="https://unpkg.com/vue@next"></script>
<script>
const component = {
data() {
return {
// Define a commodity object containing price and quantity
product: {
price: 10.quantity: 2}}},computed: {
// Calculate the total price
total() {
return this.product.price * this.product.quantity
}
}
}
const app = Vue.createApp(component)
app.mount('#app')
</script>
Copy the code
This is standard vue3 code, and totLA is always recalculated when you enter something in the input field, as shown below.
Such a function is called responsive.
Responsive data rendering is now a very important mechanism in the front end. But how exactly is this mechanism built step by step? That’s what this blog is about.
If you want to knowvue3
Response system and build history, then you should read on.
Js procedural
To understand responsiveness, you need to understand programmatic. Let’s look at the following common js code:
// Define a commodity object containing price and quantity
let product = {
price: 10.quantity: 2
}
/ / total price
let total = product.price * product.quantity;
// First print
console.log('Total price:${total}`);
// Modify the quantity of goods
product.quantity = 5;
// Print the second time
console.log('Total price:${total}`);
Copy the code
What should the above code print the first time? What should I print the second time?
Congratulations to you! That’s right, because it’s just plain js code, so it should print both times:
Total price: 20
But have you ever thought, when we go to print the second time, do you really want it to be 20?
Have you ever had the thought occur to you that if the quantity of goods changes, it would be great if the total price could follow itself? It’s human nature, and from a human point of view, it really should be that way. But the program is not so “smart.” So how can you make a program smarter?
The process of making a program “smart” is the process of responsive building.
You want to redo the operation when the data changes
You want to make your program smarter, so you start thinking, “If the data changes, I’ll just do it again.”
Just do it, and to do that, you begin to encapsulate the operations.
You define an anonymous function effect to calculate the total price of an item. And let Effect run before printing the total price. So you get the following code:
// Define a commodity object containing price and quantity
let product = {
price: 10.quantity: 2
}
/ / total price
let total = 0;
// An anonymous function to calculate the total price
let effect = () = > {
total = product.price * product.quantity;
};
// First print
effect();
console.log('Total price:${total}`); // Total price: 20
// Modify the quantity of goods
product.quantity = 5;
// Print the second time
effect();
console.log('Total price:${total}`); // Total price: 50
Copy the code
In such a code, you get the desired result: the data changes and the operation is re-executed
However, you soon discover a new problem: such code can only maintain a single total price calculation. You want it to support more computations, so what do you do?
You want to redo multiple operations when the data changes
Your code supports only one operation, and you want it to support more.
To do this, you begin a simple wrapper around your code. You do three things:
- create
Set
Array (Click to learn about Set), used to store multiple operation functions - create
track
Function, which is used to evaluateSet
To store the operation functions - create
trigger
Function to perform all of the operation functions
So you get the following code, and call this set of code reactive:
// ------------- create responsive -------------
// Set array, used to hold all operation functions
let deps = new Set(a);// Save the operation function
function track() {
deps.add(effect);
}
// trigger, execute all operation functions
function trigger() {
deps.forEach((effect) = > effect());
}
// ------------- Create data source -------------
// Declare the commodity object as the data source
let product = {
price: 10.quantity: 2
};
// State the total price
let total = 0;
// Calculate the total price of the anonymous function
let effect = () = > {
total = product.price * product.quantity;
};
// ------------- Perform reactive -------------
// Save the operation function
track();
// Calculate the total price
effect();
console.log('Total price:${total}`); // Total price: 20
// Modify the data source
product.quantity = 5;
// Data source is modified, trigger is executed, recalculate all total
trigger();
console.log('Total price:${total}`); // Total price: 50
Copy the code
You’re so proud of your creation that you start recommending it to your friends. Soon, however, a problem was raised: I wanted to apply responsiveness to a specific property of the object, not a property change and rerunce the entire calculation.
You want to: make each property individually responsive
Reactive binding object that causes a property change and rerun of all calculations. So you want to apply responsiveness to a specific property of an object, only recalculating the content associated with that property
To do this, you need to use a Map object.
A Map stores data in the form of key:val. You want the attribute to be the key and the set of operations associated with that attribute to be val. From this you construct a depsMap object for your purpose:
// ------------- create responsive -------------
// Key: collection of Val structures
let depsMap = new Map(a);// Store the operator function separately for each attribute so that each attribute has its own independent response
function track(key, eff) {
let dep = depsMap.get(key)
if(! dep) { depsMap.set(key, (dep =new Set()))
}
dep.add(eff)
}
// trigger that executes the operation function for the specified property
function trigger(key) {
// Get the deP array of the specified function
const dep = depsMap.get(key);
// Iterate through the dep, executing the operator function of the specified function
if (dep) {
dep.forEach((eff) = >eff()); }}// ------------- Create data source -------------
// Declare the commodity object as the data source
let product = {
price: 10.quantity: 2
};
// State the total price
let total = 0;
// Calculate the total price of the anonymous function
let effect = () = > {
total = product.price * product.quantity;
};
// ------------- Perform reactive -------------
// Save the operation function
track('quantity', effect);
// Calculate the total price
effect();
console.log('Total price:${total}`); // Total price: 20
// Modify the data source
product.quantity = 5;
// quantity is modified to trigger only the response of Quantity
trigger('quantity');
console.log('Total price:${total}`); // Total price: 50
</script>
Copy the code
Your client is always very picky, and soon they come up with a new problem: My program can’t have just one object! You need to make all objects responsive!
You want to make different properties of different objects individually responsive
Your reactive style needs to override all objects in your program, or your code will be meaningless!
To do this, you need to cache objects, attributes, and operations separately, which depsMap doesn’t do anymore. You need a more powerful Map so that every object has a Map. It is WeakMap.
A WeakMap object is a set of key/value pairs. The key must be an object, and the value can be arbitrary.
With WeakMap you have a depsMap for each object:
// ------------- create responsive -------------
WeakMap: Key must be an object, and val can be any value
const targetMap = new WeakMap(a)// Store the operation function separately for each attribute of the different object, so that each attribute of the different object has its own independent response
function track(target, key, eff) {
// Get the depsMap of the object
let depsMap = targetMap.get(target)
if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}// Get the depsMap attribute
let dep = depsMap.get(key)
if(! dep) { depsMap.set(key, (dep =new Set()))}// Save different object, different attributes of the operation function
dep.add(eff)
}
// trigger that executes the operation on the specified property of the specified object
function trigger(target, key) {
// Get the depsMap of the object
let depsMap = targetMap.get(target)
if(! depsMap) {return
}
// Get the deP array of the specified function
const dep = depsMap.get(key);
// Iterate through the dep, executing the operator function of the specified function
if (dep) {
dep.forEach((eff) = >eff()); }}// ------------- Create data source -------------
// Declare the commodity object as the data source
let product = {
price: 10.quantity: 2
};
// State the total price
let total = 0;
// Calculate the total price of the anonymous function
let effect = () = > {
total = product.price * product.quantity;
};
// ------------- Perform reactive -------------
// Save the operation function
track(product, 'quantity', effect);
// Calculate the total price
effect();
console.log('Total price:${total}`); // Total price: 20
// Modify the data source
product.quantity = 5;
// quantity is modified to trigger only the response of Quantity
trigger(product, 'quantity');
console.log('Total price:${total}`); // Total price: 50
Copy the code
I need to re-execute the trigger every time the data changes, which is too much trouble! What if I forget? . Customers will always put forward some requirements to change (wu) into (Li), there is no way, who let people are customers?
You want to: make different properties of different objects automatically responsive
I need to re-execute the trigger every time the data changes, your client complains.
To do this, you need to understand the “behavior of data” : you need to know when data is assigned and when it is output.
At this point you need to use two new objects:
- Proxy (here’s a video version of Happy)
- Reflect
With Proxy + Reflect you have successfully implemented listening on data:
// ------------- create responsive -------------
WeakMap: Key must be an object, and val can be any value
const targetMap = new WeakMap(a)// Store the operation function separately for each attribute of the different object, so that each attribute of the different object has its own independent response
function track(target, key, eff) {
// Get the depsMap of the object
let depsMap = targetMap.get(target)
if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}// Get the depsMap attribute
let dep = depsMap.get(key)
if(! dep) { depsMap.set(key, (dep =new Set()))}// Save different object, different attributes of the operation function
dep.add(eff)
}
// trigger that executes the operation on the specified property of the specified object
function trigger(target, key) {
// Get the depsMap of the object
let depsMap = targetMap.get(target)
if(! depsMap) {return
}
// Get the deP array of the specified function
const dep = depsMap.get(key);
// Iterate through the dep, executing the operator function of the specified function
if (dep) {
dep.forEach((eff) = >eff()); }}// Use proxy to delegate data sources for listening purposes
function reactive(target) {
const handlers = {
get(target, key, receiver) {
track(target, key, effect)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if(result && oldValue ! = value) { trigger(target, key) }return result
},
}
return new Proxy(target, handlers)
}
// ------------- Create data source -------------
// Declare the commodity object as the data source
let product = reactive({ price: 10.quantity: 2 })
// State the total price
let total = 0;
// Calculate the total price of the anonymous function
let effect = () = > {
total = product.price * product.quantity;
};
// ------------- Perform reactive -------------
effect()
console.log('Total price:${total}`); // Total price: 20
// Modify the data source
product.quantity = 5;
console.log('Total price:${total}`); // Total price: 50
Copy the code
You feel satisfied that your code is perfect. All of a sudden, I hear a voice saying, “Don’t you think every time you perform an effect, it’s anti-human?”
You want: to automate the operations
Automation! Automation! All operations should run automatically!
To make the operation run automatically, you design an effect function that takes the operation and executes it automatically
// ------------- create responsive -------------
WeakMap: Key must be an object, and val can be any value
const targetMap = new WeakMap(a)// The object of the operation function
let activeEffect = null;
// Store the operation function separately for each attribute of the different object, so that each attribute of the different object has its own independent response
function track(target, key) {
if (activeEffect) {
// Get the depsMap of the object
let depsMap = targetMap.get(target)
if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}// Get the depsMap attribute
let dep = depsMap.get(key)
if(! dep) { depsMap.set(key, (dep =new Set()))}// Save different object, different attributes of the operation function
dep.add(activeEffect)
}
}
// trigger that executes the operation on the specified property of the specified object
function trigger(target, key) {
// Get the depsMap of the object
let depsMap = targetMap.get(target)
if(! depsMap) {return
}
// Get the deP array of the specified function
const dep = depsMap.get(key);
// Iterate through the dep, executing the operator function of the specified function
if (dep) {
dep.forEach((eff) = >eff()); }}// Use proxy to delegate data sources for listening purposes
function reactive(target) {
const handlers = {
get(target, key, receiver) {
track(target, key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if(result && oldValue ! = value) { trigger(target, key) }return result
},
}
return new Proxy(target, handlers)
}
// Receive operation function, execute operation function
function effect(eff) {
activeEffect = eff;
activeEffect();
activeEffect = null;
}
// ------------- Create data source -------------
// Declare the commodity object as the data source
let product = reactive({ price: 10.quantity: 2 })
// State the total price
let total = 0;
// Calculate the total price by effect
effect(() = > {
total = product.price * product.quantity;
})
// ------------- Perform reactive -------------
console.log('Total price:${total}`); // Total price: 20
// Modify the data source
product.quantity = 5;
console.log('Total price:${total}`); // Total price: 50
Copy the code
conclusion
Vue is surprisingly responsive, and we want to know more about it, and more importantly, how it has evolved.
We start from the JS procedural, standing in the human nature began to think, what should the program look like?
We went through six major phases to get to the responsive system we wanted, and this is exactly what vuE3’s responsive system went through when it was built.