Clever Response: Deep Understanding of the Responsive Mechanism of Vue 3

Note: This is the notes of the Saint Teacher's course. The original course address is: 07 | Smart Response: Understanding the Response Mechanism of Vue 3

First, what is a response

Looking at the code below, double s depend on count, but when we modify count, it doesn't change.

let count = 1
let double = count * 2
console.log(double)//2
count = 2
console.log(double)//2

Double updates automatically when you want to implement count changes. You need something to listen for count in real time, and when you see that count has changed, call double = count * 2 again to update. The following:

Create an object to listen on object
let count = 1   //object starts listening for count
let double = count * 2
console.log(double)//2
count = 2  //object listens for counts to change. Call double = count * 2 again to update double
console.log(double)//2, what we actually want is that it becomes 4, which should be done by the listener object calling double = count * 2 again

Second, the principle of response

Three responsive solutions have been used in Vue, defineProperty, Proxy, and value setter.

Let's start with the defineProperty API for Vue 2, which gives you detailed API descriptions that give you direct access to the MDN documentation Object.defineProperty() - JavaScript | MDN (mozilla.org) To understand.

In the code below, we define an object obj that uses defineProperty to listen for the count property. This allows us to intercept the value attribute of the obj object, execute the get function when reading the count attribute, execute the set function when modifying the count attribute, and recalculate the double inside the set function.

let getDouble = n=>n*2
let obj = {}
let count = 1
let double = getDouble(count)

Object.defineProperty(obj,'count',{
    get(){
   		return count
    },
    set(val){
         count = val
         double = getDouble(val)
    }
})//This is the listener mentioned above
console.log(double)  // Print 2
obj.count = 2
console.log(double) // Print 4 has a sense of automatic change

In this way, we can achieve a simple and responsive function.

However, the defineProperty API, as the principle of Vue 2's implementation of responsiveness, has some flaws in its syntax. For example, in the following code, if we delete the obj.count attribute (obj.count is deleted from heap space), the set function will not execute, and the double is still the previous value (double or global variable). That's why in Vue 2, we need a special function called $delete to delete data.

delete obj.count
console.log(double) // doube or 4

The responsiveness mechanism of Vue 3 is based on Proxy, which resolves the defect of Vue 2 responsiveness.

Proxy, like Math and Date, is a built-in object for js.

Let's look at the code below, in which we proxyed obj with new Proxy, and then read, modify, and delete objects with get, set, and deleteProperty functions to achieve responsive functionality.

let obj = {}
let count = 1
let getDouble = n=>n*2
let double = getDouble(count)
let proxy = new Proxy(obj,{
  get : function (target,prop) {
	  return target[prop]
  },
  set : function (target,prop,value) {
	  target[prop] = value;
	  if(prop==='count'){
		  double = getDouble(value)
	  }
  },
  deleteProperty(target,prop){
	  delete target[prop]
	  if(prop==='count'){
		  double = NaN
	  }
  }
})
proxy.count=count
console.log(obj.count,double)//1 2
proxy.count = 2
console.log(obj.count,double) //2 4
delete proxy.count
// After deleting the attribute, when we print the log, the output will be undefined NaN
console.log(obj.count,double) //undefined NaN

We can see from this that Proxy implements functions similar to Vue 2's defineProperty, both of which trigger the set function when the user modifies the data, thereby enabling automatic updating of double s. Proxy also improves several DefneProperty flaws, such as the ability to listen for attribute deletions.

Proxy listens for objects, not for specific attributes, so it can proxy not only those attributes that do not exist at the time of definition, but also richer data structures, such as Map, Set, and so on, and we can also proxy delete operations through deleteProperty.

Of course, to help understand Proxy, we can also write code about double s in the set and deleteProperty functions. For example, in the code below, the Vue 3 reactive function can turn an object into responsive data, and the reactive is based on Proxy. We can also print data through the watchEffect after the obj.count modification.

import { reactive, watchEffect, computed } from "vue";
let obj = reactive({
  count: 1
});
let double = computed(() => obj.count * 2);
setTimeout(() => {
  obj.count = 2;
}, 3000);
watchEffect(() => {
  console.log("Data was modified", obj.count, double.value);
});

With Proxy, the responsive mechanism is more complete. However, there is another logic for the responsive implementation in Vue 3, which is to listen using the get and set functions of the object. This responsive implementation can only intercept the modification of one property, which is also the implementation of ref API in Vue 3. In the code below, we intercepted the value attribute of count and the set operation to achieve similar functionality.

let getDouble = n => n * 2
let _value = 1
double = getDouble(_value)

let count = {
  get value() {
    return _value
  },
  set value(val) {
    _value = val
    double = getDouble(_value)
  }
}
console.log(count.value,double)
count.value = 2
console.log(count.value,double)

A comparison table of the three implementation principles is as follows:

Third, customize responsive data

Code for todolist after setup refactoring. This code uses watchEffect, which synchronizes the data over the local store when the data changes, so that we synchronize the todolist with the local store.

function useStorage(name, value=[]){
    let data = ref(JSON.parse(localStorage.getItem(name)|| value))
    watchEffect(()=>{
        localStorage.setItem(name,JSON.stringify(data.value))
    })
    return data
}

Further, we can pull out a useStorage function directly to synchronize any data-responsive changes to local storage on a responsive basis. Let's start with the following code, ref takes data from local storage, encapsulates it as responsive and returns it, watchEffect synchronizes local storage, and useStorage is a function that can be pulled out into a file and placed in the tool functions folder.

function useStorage(name, value=[]){
    let data = ref(JSON.parse(localStorage.getItem(name)|| value))
    watchEffect(()=>{
        localStorage.setItem(name,JSON.stringify(data.value))
    })
    return data
}

In the project, we use the following code to turn ref into useStorage, which is also the greatest advantage of the Composition API, i.e., the ability to freely split up separate functions.

let todos = useStorage('todos',[])

function addTodo() {
  ...code
}

We can encapsulate the data used in daily development, whether it is browser's local storage or network data, into responsive data and use the mode of responsive data development in a unified way. This way, when we develop a project, we just need to modify the corresponding data.

Fourth, the Vueuse Toolkit

Our own encapsulated useStorage is simply a response object to update data and synchronize the local Storage. Similarly, we can encapsulate more user-like functions like the useStorage function to encapsulate any data or browser properties you use in actual development as responsive data, which can greatly improve our development efficiency.

There is already a similar collection of tools in the Vue community, VueUse, that encapsulates common properties in development as responsive functions.

Installation of vueuse:

npm install @vueuse/core

Simple use:

import { useMouse } from "@vueuse/core";
// "x" and "y" are refs
const { x, y } = useMouse();
console.log( x.value, y.value);

Keywords: Javascript Front-end Vue

Added by webtailor on Tue, 23 Nov 2021 22:56:05 +0200