Take you to understand the most important API s in vue3 - ref and reactive

Knowledge Prospect: suppose you know vue3 the knowledge about setup in composite API. If you are just beginning to study, you can have a look Official documents , or My article.

In vue3, there are two important APIs - ref and reactive. If you don't understand the principles of these two APIs, it can be said that you haven't learned vue3 well.

However, these two API s are difficult to understand. I have experience in vue2. After I have completed the documents of vue3, I feel that these two goods are the most difficult to understand, because they involve a lot of underlying knowledge: JS data type, memory stack and object Defineproperty, ES6 Proxy, so today, I'll take you to chew this hard bone.

Part I "response" of data

1, The goal of ref and reactive -- realizing the "responsiveness" of data

What are ref and reactive for? Is to turn data into "responsive".

Note the word "responsive", which will be mentioned repeatedly later, because without "responsive", the data-driven view of vue cannot be realized.

What exactly is "responsive"?

If you are familiar with vue, you must be familiar with the following code:

<div id="counter">
  Counter: {{ counter }}
</div>
export default {
  data() {
    return {
      counter: 0
    }
  },
  mounted() {
    setInterval(() => {
      this.counter++
    }, 1000)
  }
}

In the above example, the counter will be incremented every second, and you will see how the rendered DOM changes:

This is the core idea of Vue - data-driven view. As you can see, data and DOM are associated, and everything is "responsive". When the data (JS) counter changes, the view layer (HTML) will change.

The data counter associated with the DOM is the "responsive" data.

Now you have understood vue's thought, but just knowing the thought is not enough. We also need to understand the principle of vue.

Therefore, I will take you further to explore: how vue realizes the "responsiveness" of data.

2, Response of vue2

1.vue2 implementation of response - object defineProperty

The response expression of vue2 is through object The defineproperty (data hijacking) method has two types of processing for objects and arrays:

  • Object: hijack (monitor / intercept) the reading and modification of the existing property value of the object through defineProperty
  • Array: a series of methods to update elements by rewriting the array to update the array

When you pass an ordinary JavaScript object into the Vue instance as the "data" option, Vue will traverse all the properties of the object and use Object.defineProperty Turn all these properties into getter/setter.

Object.defineProperty(data, 'count', {
    get () {}, 
    set () {}
})

These getters / setters are invisible to the user, but internally they enable Vue to track dependencies and notify changes when properties are accessed and modified. It should be noted that different browsers format getters / setters differently when printing data objects on the console, so it is recommended to install them vue-devtools To get a more user-friendly interface to check data.

Each component instance corresponds to a {watcher} instance, which records the "touched" data property as a dependency during component rendering. Then, when the setter of the dependency is triggered, it will notify the watcher to re render its associated components.

 

2.vue2 responsive questions

Although the "responsiveness" of data is implemented, Vue} cannot detect changes in arrays and objects. This results in: object The data response implemented by defineproperty still has some hard wounds:

1) Hard injury 1: the interface will not be automatically updated if the object directly adds new attributes or deletes existing attributes

Due to object Defineproperty can only represent a property. The object can directly add a new property or delete an existing property. This situation cannot be monitored.

var vm = new Vue({
  data:{
    a:1
  }
})

// `vm.a ` is responsive

vm.b = 2
// `vm.b ` is non responsive

resolvent:

① Update 1 attribute, using VM$ Set method

To solve this problem, vue2 provides VM$ Set instance method, which is also the global Vue Alias of set} method:

this.$set(this.someObject,'b',2)

② Update multiple attributes, using object assign()

Sometimes you may need to assign multiple new properties to an existing object, such as using {object Assign () or extend(). However, the new property added to the object will not trigger the update. In this case, you should create a new object with the original object and the property of the object to be mixed in.

// Replace ` object assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

2) Hard injury 2: directly replace array elements or update length through subscript, and the interface will not be updated automatically

for instance:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // Not responsive
vm.items.length = 2 // Not responsive

resolvent:

① For the problem that subscripts replace array elements without updating the interface, use VM$ Slice method of set or array

And VM can be implemented in the following two ways Items [indexofitem] = newvalue has the same effect, and will also trigger status update in the responsive system:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

You can also use vm.$set Instance method, which is the global method Vue An alias for set +

vm.$set(vm.items, indexOfItem, newValue)

② For the problem of updating the length of the array without updating the interface, use the slice method of the array

To solve the second type of problem, you can use {splice:

vm.items.splice(newLength)

Since there are so many problems in the "response" of vue2, how does vue3 solve these problems? Let's look down.

3, Vue3's response

1.vue3 implementation of response -- Proxy object

For object The disadvantage of defineproperty is that a new object is introduced in ES6—— Proxy (object proxy)

1) Proxy object

Proxy} object: used to create a proxy for an object. It is mainly used to change the default access behavior of the object. In fact, it adds a layer of interception before accessing the object. Any access behavior to the object will be intercepted through this layer. In this layer of interception, we can add custom behavior. The basic syntax is as follows:

/*
 * target: Target object
 * handler: The configuration object is used to define the interception behavior
 * proxy: Proxy Instance of constructor
 */
var proxy = new Proxy(target,handler)

2) Basic usage of Proxy} object

Take a simple example:

// Target object
var target = {
	num:1
}
// Custom access interceptor
var handler = {
  // receiver: the object on which the operation occurs, usually the agent
  get:function(target,prop,receiver){
    console.log(target,prop,receiver)
  	return target[prop]*2
  },
  set:function(trapTarget,key,value,receiver){
    console.log(trapTarget.hasOwnProperty(key),isNaN(value))
  	if(!trapTarget.hasOwnProperty(key)){
    	if(typeof value !== 'number'){
      	throw new Error('Input parameter must be numeric')
      }
      return Reflect.set(trapTarget,key,value,receiver)
    }
  }
}
// Create proxy instance dobuleTarget for target
var dobuleTarget = new Proxy(target,handler)
console.log(dobuleTarget.num) // 2

dobuleTarget.count = 2
// When a proxy object adds a new attribute, the target object is also added
console.log(dobuleTarget) // {num: 1, count: 2}
console.log(target)  // {num: 1, count: 2}
// A new attribute is added to the target object, and the Proxy can listen to it
target.c = 2
console.log(dobuleTarget.c)  // 4. You can listen to the new attributes of target
 Copy code

In the example, we created the Proxy dobuleTarget of target through the Proxy constructor, that is, we Proxy the whole target object. At this time, the access to the dobuleTarget attribute will be forwarded to the target, and the user-defined handler object is configured for the access behavior. Therefore, any access to the properties of the target object through the dobuleTarget will perform the interception operation customized by the handler object.

The Proxy object can intercept any (13) operations on any attribute of data, including reading and writing attribute values, adding attributes, deleting attributes, etc After these operations are intercepted, a "trap function" in response to a specific operation will be triggered.

The 13 "trap functions" are shown in the figure below:

Trap functionOverridden properties
getRead a value
setWrite a value
hasin operator
deletePropertyObject.getPrototypeOf()
getPrototypeOfObject.getPrototypeOf()
setPrototypeOfObject.setPrototypeOf()
isExtensibleObject.isExtensible()
preventExtensionsObject.preventExtensions()
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor()
definePropertyObject.defineProperty
ownKeysObject.keys() Object.getOwnPropertyNames() and object getOwnPropertySymbols()
applyCall a function
constructCall a function with new

Proxy} object is larger than object Defineproperty is more awesome! It solves all vue2 responsive hard injuries! To sum up:

  1. Proxy is a proxy for the whole object, while object Defineproperty can only delegate a property.
  2. Add a new attribute on the object. The Proxy can listen to the object Defineproperty cannot be.
  3. The array is newly added and modified. The Proxy can listen to object Defineproperty cannot be.
  4. If all the internal properties of the object are recursive proxies, the Proxy can only recurse when calling, and object Definepropery needs to complete all recursion at once, and its performance is worse than Proxy.
  5. Proxy is not compatible with ie, object Defineproperty is incompatible with IE8 and below
  6. Proxy is better than object Defineproperty is much more convenient.

 

 

2) Basic usage of Proxy} object

The responsive implementation of Vue3 uses this powerful Proxy object. Vue will wrap the data in a package with {get} and} set} handlers Proxy Yes. When the Proxy} object listens to the data change of, Through reflect : dynamically perform specific operations on the corresponding attributes of the proxy object. The specific code is as follows:

new Proxy(data, {
	// Intercept reading attribute values
    get (target, prop) {
    	return Reflect.get(target, prop)
    },
    // Intercept setting property values or adding new properties
    set (target, prop, value) {
    	return Reflect.set(target, prop, value)
    },
    // Block delete attribute
    deleteProperty (target, prop) {
    	return Reflect.deleteProperty(target, prop)
    }
})

proxy.name = 'tom'   

Case:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Proxy And Reflect</title>
</head>
<body>
  <script>
    
    const user = {
      name: "John",
      age: 12
    };

    /* 
    proxyUser User is the proxy object and user is the proxy object
    All the latter operations operate the internal properties of the proxy object through the proxy object
    */
    const proxyUser = new Proxy(user, {

      get(target, prop) {
        console.log('hijack get()', prop)
        return Reflect.get(target, prop)
      },

      set(target, prop, val) {
        console.log('hijack set()', prop, val)
        return Reflect.set(target, prop, val); // (2)
      },

      deleteProperty (target, prop) {
        console.log('hijack delete attribute', prop)
        return Reflect.deleteProperty(target, prop)
      }
    });
    // Read attribute value
    console.log(proxyUser===user)
    console.log(proxyUser.name, proxyUser.age)
    // Set attribute value
    proxyUser.name = 'bob'
    proxyUser.age = 13
    console.log(user)
    // Add attribute
    proxyUser.sex = 'male'
    console.log(user)
    // Delete attribute
    delete proxyUser.sex
    console.log(user)
  </script>
</body>
</html>

2.Proxy recursive proxy

Proxy only proxies the outer properties of the object. Examples are as follows:

var target = {
  a:1,
  b:{
//  The Proxy cannot Proxy the attribute values (c, d, e) of the inner layer of the object.
    c:2,
    d:{e:3}
  }
}
var handler = {
  get:function(trapTarget,prop,receiver){
    console.log('trigger get:',prop)
    return Reflect.get(trapTarget,prop)
  },
  set:function(trapTarget,key,value,receiver){
    console.log('trigger set:',key,value)
    return Reflect.set(trapTarget,key,value,receiver)
  }
}
var proxy = new Proxy(target,handler)

proxy.b.d.e = 4 
// The output triggers get:b, which shows that the Proxy only proxies the outer attributes of the object.

Then, how to implement the agent for the change of inner attributes? The answer is to set the proxy recursively:

var target = {
  a:1,
  b:{
  	c:2,
    d:{e:3}
  }
}
var handler = {
  get:function(trapTarget,prop,receiver){
    var val = Reflect.get(trapTarget,prop)
    console.log('get',prop)
    if(val !== null && typeof val==='object'){
    	return new Proxy(val,handler) // Agent inner layer
    }
    return Reflect.get(trapTarget,prop)
  },
  set:function(trapTarget,key,value,receiver){
    console.log('trigger set:',key,value)
    return Reflect.set(trapTarget,key,value,receiver)
  }
}
var proxy = new Proxy(target,handler)
proxy.b.d.e
// Output: all proxied
// get b
// get d
// get e 

It can be seen from the recursive Proxy that if all recursive proxies are required inside the object, the Proxy can set the Proxy recursively only when calling.

4, Reactive and ref of vue3

As long as you understand the Proxy object above, it will be much more convenient for us to understand vue3's c reative. Let's take a closer look:

1.reative

Meaning: Convert "reference type" data into "responsive" data, that is, wrap the data of value type with the data of programming responsive reference type

Type: function

Parameter: the reactive parameter must be an object (json/arr)

Essence: wrap the incoming data into a Proxy object

Handwritten c reative function implementation:

const reactiveHandler = {
  get (target, key) {

    if (key==='_is_reactive') return true

    return Reflect.get(target, key)
  },

  set (target, key, value) {
    const result = Reflect.set(target, key, value)
    console.log('Data updated, To update the interface')
    return result
  },

  deleteProperty (target, key) {
    const result = Reflect.deleteProperty(target, key)
    console.log('Data deleted, To update the interface')
    return result
  },
}


/* 
Customize reactive
*/
function reactive (target) {
  if (target && typeof target==='object') {
    if (target instanceof Array) { // array
      target.forEach((item, index) => {
        target[index] = reactive(item)
      })
    } else { // object
      Object.keys(target).forEach(key => {
        target[key] = reactive(target[key])
      })
    }

    const proxy = new Proxy(target, reactiveHandler)
    return proxy
  }

  return target
}




/* Test custom reactive */
const obj = {
  a: 'abc',
  b: [{x: 1}],
  c: {x: [11]},
}

const proxy = reactive(obj)
console.log(proxy)
proxy.b[0].x += 1
proxy.c.x[0] += 1

Seeing this, you may think: since the reactive function has implemented the "response" of data, why is there another function that implements the "response" - ref?

Now let me answer this question. You need to note that in the definition of reactive function, there is a sentence: Convert "reference type" data to "responsive" data. What is this "reference type"?

This starts with the data type of JS

Data type of JS

1. Stack and heap

stack is the automatically allocated memory space, which is automatically released by the system; heap is dynamically allocated memory, and its size may not be automatically released

2. Data type

JS has two data types:

Basic data types: Number, String, Boolean, Null, Undefined, Symbol (ES6). These types can directly operate the actual values saved in variables.

Reference data type: Object (in JS, all except the basic data type are objects, data are objects, functions are objects, and regular expressions are objects)

3. Basic data type (stored in stack)

Basic data types refer to simple data segments stored in the stack. The data size is determined and the memory space can be allocated. They are stored directly by value, so they can be accessed directly by value

var a = 10;
var b = a;
b = 20;
console.log(a); // 10
console.log(b); // 20

The following figure illustrates the assignment process of this basic data type:

 

4. Reference data type (for objects stored in heap memory, the size of each space is different, and specific configuration shall be made according to the situation)

A reference type is an object stored in the heap memory. A variable is actually a pointer stored in the stack memory (the reference address in the heap memory is saved), and this pointer points to the heap memory.

The reference type data stored in stack memory is actually the reference address of the object in heap memory. Through this reference address, you can quickly find the objects saved in heap memory.

var obj1 = new Object();
var obj2 = obj1;
obj2.name = "I";
console.log(obj1.name); // I

Indicates that the two reference data types point to the same heap memory object. Obj1 is assigned to obj2. In fact, a copy of the reference address of the heap memory object in the stack memory is copied to obj2, but they actually point to the same heap memory object. Therefore, modifying obj2 is actually modifying that object, so it can be accessed through obj1.

 

var a = [1,2,3,4,5];
var b = a;//The data passed to the variable in the object is of reference type and will be stored in the heap;
var c = a[0];//Pass value, assign the attribute in the object / the array item in the array to the variable. At this time, the variable C is the basic data type and is stored in the stack memory; Changing the data in the stack does not affect the data in the heap
alert(b);//1,2,3,4,5
alert(c);//1
//change value 
b[4] = 6;
c = 7;
alert(a[4]);//6
alert(a[0]);//1

From the above, we can know that when I change the data in b, the data in a also changes; But when I change the data value of c, a doesn't change.

This is the difference between value transmission and address transmission. Because a is an array and belongs to the reference type, when it is given to b, it passes the address in the stack (equivalent to creating a new "pointer" with a different name), rather than the object in the heap memory. c is just a data value obtained from a heap memory and stored on the stack. Therefore, when modifying b, it will go back to the a heap for modification according to the address, while c will modify it directly in the stack and cannot point to the memory of the a heap.

The following dynamic diagram vividly explains the difference between value transmission and address transmission. The left side is address transmission and the right side is value transmission,

 

Proxy objects can only proxy objects of reference type. How do you implement responsiveness in the face of basic data types?

vue's solution is to change the basic data type into an object, add a value attribute to the object, and then use the reactive method to turn it into a responsive Proxy object.

So the essence of ref is

ref(0) --> reactive( { value:0 })

ref

Reference link: Re learn JS | Proxy and object Usage and difference of defineproperty

Difference between JS basic data type and reference data type and its deep and shallow copy - brief book

Keywords: Javascript Front-end Vue Vue.js

Added by perficut on Fri, 14 Jan 2022 19:37:38 +0200