What is reactive?

As a front-end, one of the things we hear most about “responsive” is a responsive web page, where the browser’s viewport changes as the page displays. The conclusion is that when one thing changes, another thing changes. So data responsiveness, as the name suggests, is when the data changes, so does the page.

How is data responsiveness implemented?

Data responsiveness starts with data. Let’s take the data property in our Vue parameter as an example. All we need to do is listen to whatever changes are made to the data in the data. Since we want to listen for changes to the properties of the object, we obviously can’t change the properties of the object directly (that way we can’t listen). We need to use a proxy method to change the properties of the object. The idea is to use an object’s virtual properties to access and manipulate our real objects.

function proxy({data}){
            let obj = {};
            Object.defineProperty(obj,'n', {get(){
                    return data.n;
                },
                set(value){
                    if(value < 0) return; // There are more restrictions on datadata.n = value; }})return obj;
        }
let obj2 = proxy({data: {n:0}});
        console.log(obj2.n);
        console.log(obj2.n = -1);
        console.log(obj2.n);
        console.log(obj2.n = 3);
        console.log(obj2.n);
Copy the code

So we’re done listening for the data when we modify it internally. The problem is, if we modify it externally, the changes can’t be listened for.

	 function proxy({data}){
            let obj = {};
            Object.defineProperty(obj,'n', {get(){
                    return data.n;
                },
                set(value){
                    if(value < 0) return; // Restrict datadata.n = value; }})return obj;
        }
        let tempObj = {
            n:0
        };
        tempObj.n = -1;
        let obj2 = proxy({data:tempObj});
        console.log(obj2.n); // -1
Copy the code

We can see that we’ve bypassed the agent, we’ve managed to modify the data without being detected, obviously that’s not what we want so how do we do that? Our solution is to use a variable to save the attributes of the original object, and then use virtual attributes to overwrite the previous attributes, so that when we modify the attributes externally, the changes of the attributes can also be monitored, this part is called “listening”.

          function proxy({data}){
            / / to monitor
            let value = data.n;
            Object.defineProperty(data,'n', {get(){
                    return value;
                },
                set(newValue){
                    if(newValue < 0) return; value = newValue; }})/ / agent
            let obj = {};
            Object.defineProperty(obj,'n', {get(){
                    return data.n;
                },
                set(value){
                    if(value < 0) return; // Restrict datadata.n = value; }})return obj;
        }
        let tempObj = {
            n:0
        };
        let obj2 = proxy({data:tempObj});
        console.log(obj2.n); / / 0
        tempObj.n = -1;
        console.log(obj2.n); / / 0
Copy the code

In this way, we are simply done. Whether we modify the object outside or inside the instance object, we can listen to the data changes. After listening to the data changes, we can use render to re-render the data to the interface.

Some bugs about Data:

Let’s start with a few examples:

<template>
  <div id="app">
    {{obj.a}}
    {{b}}
  </div>
</template>

<script>

export default {
  name: 'App'.data(){
    return {
      obj: {a:1}}}}</script>
Copy the code







We define an object in data and refer to its properties. We can see it displayed in the interface, but if we refer to an undefined property in template, the console will report an error.

So if we go ahead and change b toobj.bWhat’s going to happen?

<template>
  <div id="app">
    {{obj.a}}
    {{obj.b}}
  </div>
</template>

<script>

export default {
  name: 'App'.data(){
    return {
      obj: {a:1}}}}</script>
Copy the code







We can see that our interface is neither displayed nor the console is reporting errors, so we assume that Vue can only listendataLayer 1 data changes. So how can we prevent this bug?



We have two options. The first is to add one to OBj ahead of timeundefinedProperty, the second method is to useVue.set(this.obj,'b',value);This can also achieve the purpose of Vue listening.

<template>
  <div id="app">
    {{obj.a}}
    {{obj.b}}
    <button @click="setB">set b</button>
  </div>
</template>

<script>

export default {
  name: 'App'.data(){
    return {
      obj: {a:1.b:undefined}}},methods: {setB(){
      // this.obj.b = 2;
      this.$set(this.obj,'b'.2); }}}</script>
Copy the code

So again, what if we need to add an element to the array? We’re not going to be able to use Vue. Set () for arrays so you can try it out. We know that the methods of modifying array elements include push, POP and so on. The native methods of these methods obviously cannot be listened on. We found that in Vue, if the push method of array is directly used to add data, the responsiveness can be completed.

<template>
  <div id="app">
    {{arr}}
    <button @click="push">push 4</button>
  </div>
</template>

<script>

export default {
  name: 'App'.data(){
    return {
      arr: [1.2.3]}},methods: {push(){
      this.arr.push(4); }}}</script>
Copy the code



So we can assume that Yu Creek has changed the native method of arrays. Let’s print this array:







It can be seen from the figure that Yuxi added a layer of prototype to the prototype chain of the array and added push, POP and other methods to complete the monitoring of the elements when modifying the elements of the array.



So how does this work? Here’s just the general idea:



We create a new class and let our classInheriting Array objects, we add one to our created classPush instance methodThis method first calls the native arraypushMethod, and then use a set for Vue to listen on the pushed element.

class VueArray extends Array{
	push(. args){
		const oldLength = this.length // this
		super.push(... args)for(let i = oldLength; i<this.length; i++){
			Vue.set(this, i, this[i])
		}
	}
}
Copy the code

General idea code.

To sum up:

Regarding the data responsiveness, we give priority to the synchronous update of the interface when data is modified, so we need to monitor the modification of data in data. The monitoring method mainly uses “proxy” internally and “monitor” externally, and the core is defineProperty method. After that, we talked about some bugs about data and solutions. In the method of array, Yuxi also encapsulated push, POP and other methods in Vue again.