Why should vue3 replace defineProperty with proxy

  • 2021-08-31 07:00:57
  • OfStack

Before that, we need to understand the core idea of vue, mutable

Whether it is vue2 or vue3, in the process of implementation, the core concept 1 keeps stable, and the concept of variable data source is the core to realize the change and update of the whole UI

In the simplest way, initialize the data to generate the page, modify the source data directly to trigger the update, and render the page again

Anyone who pays attention to vue knows that proxy is used instead of defineProperty in vue3.

When using vue2, we often encounter a problem. Adding a new object attribute obj. a = 1 will not be hijacked by vue2, and must be updated by using the $set method provided by vue2

The reason for this must be clear to everyone, because defineProperty can only hijack one of the attributes of the current object


const a = {
  b: 1,
};
Object.defineProperty(a, 'b', {
  set: function() {},
  get: function() {},
});

When we add a new attribute to a object, the newly added attribute is not hijacked by defineProperty. Although a new attribute is still successfully generated on the corresponding object, we know that vue2 hijacks data through setter and getter of defineProperty. Since the newly added data is not hijacked, no matter how it is updated, the page will not be rendered again

In vue3, using proxy for data proxy is completely free of this concern


const p = new Proxy({
  a: 1,
  b: 2,
}, {
  get: function(obj, value) {
    console.log('get', obj, value);
    return Reflect.get(obj, value);
  },
  set: function(obj, prop, value) {
    console.log('set', obj, prop, value);
    return Reflect.set(obj, prop, value);
  },
})

proxy is a proxy for data that can respond to new attributes. When one attribute is added, it can respond to get to proxy the current object

How does vue3 work through the proxy proxy

First of all, you can look at several new apiref, reactive, effect and computed in vue3


ref And reactive
const normal = ref(0);
const state = reactive({
  a: 1,
  b: 2,
})

reactive is also used in the compatible processing of vue2 in vue3, that is, instance. data = reactive (data), and the whole data attribute is proxy by reactive

We know that data in vue2 uses Object. definePerproty for data hijacking, so in reactive, how does it use proxy for data proxy to be compatible with the old writing mode and the new compositionApi

ps: Because in reactive, it is only through proxy to check and proxy the incoming data, and the most important ones are set and get, so we should go directly to the base. After all, we can eat hot tofu in a hurry

get

You can analyze 1, vue2 or vue3. There will be no difference in the main content of what you do for data acquisition

Get the data of key that is currently needed Dependent acquisition

However, for the use of proxy in vue3, an additional part of compatibility is made here

If the obtained data is 1 object, reactive will be used to proxy the data once again for the object If it is a data proxy of shallow type, it directly returns the currently obtained data

effect Dependent Acquisition

vue has corresponding computed and watch besides the normal data proxy of data, and watchEffect and computed methods directly used in vue3 can directly generate corresponding contents

Their data updates and dependency processing rely on get on the current data data for dependency collection

Look at the core code from the beginning to the end


// targetMap Object for the current data of all agents 1 A Map Set 
// depsMap Each of the data of the current agent 1 A Key Corresponding to Map Set 
// dep Object in the current agent's data key Corresponding dependency of 
// activeEffect Currently defined by effect Or computed Generated data 

let depsMap = targetMap.get(target)
if (!depsMap) {
  targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
  depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
  dep.add(activeEffect)
  activeEffect.deps.push(dep)
}

If you simply read from this code, you may have a certain difficulty. From another angle, start from the overall usage of vue3 and return to interpret this code


setup() {
  const b = reactive({
    c: 1,
    b: 2,
  });
  // effect Yes vue In reactivity The method that the package returns directly 
  const a = effect(() => {
    return b.c;
  })
}

First of all, b defined by reactive is used in effect. From the surface phenomenon, we can know that when b. c changes, a will also change synchronously

The reason for this change is the activeEffect in the above source code. When the created effect is called, the activeEffect will be set to itself, and the corresponding callback function will be executed. The call of the function will trigger the getter of each used data, and the corresponding effect dependency will be injected into each used data

As for the reason why the dependency acquisition of one attribute is so complicated, it is because proxy is used. proxy proxies the whole object, so it is impossible to directly bind the current field in getter like vue2 using Obect. defineProperty. Therefore, in vue3, the whole object is directly regarded as an Map, and key of each Map is a corresponding attribute, while value is all objects that depend on the current attribute

set

With get, it still maintains the original thinking and mode

Set current data Publish subscribed data (trigger dependent updates)

In vue3, there are some differences, after all, it is a library pulled out separately

If you call effect directly, when the detected data changes, it will be modified directly If you call watch or watchEffect, you will follow the scheduling scheme of vue itself

Therefore, if you want to update the current data directly, you can give priority to using effect, which will update 1 point faster than watchEffect. The disadvantage is that many things may have to be written by yourself =, =

As for how to achieve it, it is actually very simple

Gets the dependencies of the currently updated data Packet enters Set waiting to run Execute

But there is a special processing, for the array of length attribute, this attribute is a definite difference, next specifically talk about the array operation in vue3

Array

In vue2, for the array is done more than 1 layer of processing, proxy array of the basic method, this is because the use of Object. defineProperty in the array above the natural disadvantages

The specific reason is clearly written in the document of vue, so it will not be described in detail here

Document address

The use of proxy in vue3 perfectly solves this problem, just because proxy can listen for changes in arrays and do a test


const a = new Proxy([1,2], {
  get: function(obj, prop) {
    console.log('get', obj, prop);
    return Reflect.get(obj, prop);
  },
  set: function(obj, prop, value) {
    console.log('set', obj, prop, value);
    return Reflect.set(obj, prop, value);
  },
});
a.push(1);

get [1,2] push
get [1,2] length
set [1,2] 2 1
set [1,2, 1] length 3

When we proxy an array, directly call push to insert a new data, we can obviously see that getter and setter will be called twice, one is the called push method, and the other is the length of the array length, that is to say, proxy will not only detect the method we are currently calling, but also know whether our data length has changed

See here, may have a doubt, push is the current array operation, but there are also some methods in the array will return a new array, proxy will also generate the new array proxy, here we take splice for example


// a= [1,2]
a.splice(0, 1)

get [1,2] push
get [1,2] length
get [1,2] constructor
get [1,2] 0
get [1,2] 1
set [1,2] 0 2
set [2,empty] length 1

In terms of expression, the array after proxy proxy will only listen to the contents of the current array, that is, the changes of the newly generated array after calling splice will not be proxy

Now let's go back to the trigger method of vue3, which is the dependency update triggered by vue after set is completed. In addition to normal execution, let's look at the optimization for arrays


// add The method is to add the current dependency to the 1 In an array waiting to be updated 
else if (key === 'length' && isArray(target)) {
  depsMap.forEach((dep, key) => {
   if (key === 'length' || key >= (newValue as number)) {
    add(dep)
   }
  })
} 

Because we know that set will be carried out many times when we operate the array once, so if set has to update the dependency every time, it will cause a waste of performance, so in vue3, only when set length will call add method, and then all updates will be carried out in the whole system

Conclusion

It must be said that proxy is much more powerful than defineProperty, which not only solves the historical problems of vue, but also makes the experience of vue higher, and removes many necessary methods because of defineProperty, simplifying the package size of vue

Although the compatibility of proxy is much worse than that of defineProperty, IE has been abandoned basically in vue, so if your project needs to run under ie, please give up the option of vue and use the lower version of react, hahahahaha

In the mobile side is basically not limited by this version, is the low version can not use proxy, I believe to find polyfill is able to find


Related articles: