Objective
Recently died for a period of time Vue source code, think about to think that still want to output something, we first come from the Vue provided by the Vue.set () and this. $set () These two APIs to see how it is implemented internally.
Vue.set () and this. $set () Application scenario
Usually do the project at the time will not be the array or object to do such a SAO operation, the results found that, hey ~ ~, he meow, how the page did not re-render.
const vueInstance = new Vue({ data: { arr: [1, 2], obj1: { a: 3 } }});vueInstance.$data.arr[0] = 3; // 这种骚操作页面不会重新渲染vueInstance.$data.obj1.b = 3; // 这种骚操作页面不会重新渲染
Checked the official documents and found that they had already said that.
Vue.set () Adds a property to the responsive object and ensures that the new property is also responsive and triggers the view update. It must be used to add a new property to the responsive object because Vue cannot detect normal new properties (such as This.myObject.newProperty = ' Hi ')
So according to the official website of the wording, we should use the following:
Vue.set(vueInstance.$data.arr, 0, 3); // 这样操作数组可以让页面重新渲染vueInstance.$set(vueInstance.$data.arr, 0, 3); // 这样操作数组也可以让页面重新渲染Vue.set(vueInstance.$data.obj1, b, 3); // 这样操作对象可以让页面重新渲染vueInstance.$set(vueInstance.$data.obj1, b, 3); // 这样操作对象也可以让页面重新渲染
Vue.set () and this $set () implementation principle
It's time to look at the source of these two APIs, let's take a look at the source code of Vue.set ():
import { set } from '../observer/index'...Vue.set = set...
Take a look at this. $set () Source code:
import { set } from '../observer/index'...Vue.prototype.$set = set...
As a result, we found that Vue.set () and this. $set () These two APIs are basically identical in implementation, using the SET function. The set function is from the. /observer/index file, the difference is that Vue.set () is binding the set function on the Vue constructor, this. $set () is to bind the set function to the Vue prototype.
Next we are based on: Find the Set function in/observer/index:
function set (target:array<any> | Object, Key:any, Val:any): any {if (Process.env.NODE_ENV!== ' production ' && (Isundef (target) | | isprimitiv E (target)) {warn (' cannot set reactive property on undefined, null, or primitive value: ${(Target:any)} ')} if (A Rray.isarray (target) && Isvalidarrayindex (key) {target.length = Math.max (target.length, key) Target.splice (Key, 1, Val) return Val} if (Key in Target &&!) ( Key in Object.prototype)) {Target[key] = val return val} const OB = (target:any). __ob__ if (Target._isvue | | ( OB && Ob.vmcount)) {Process.env.NODE_ENV!== ' production ' && warn (' Avoid adding reactive propert IES to a Vue instance or their root $data ' + ' at Runtime-declare it upfront in the data option. ' ) return Val} if (!ob) {Target[key] = val return val} definereactive (Ob.value, Key, Val) ob.dep.notify () Return val}
We found that the SET function receives three parameters, target, Key, Val, where the value of target is an array or an object, which corresponds to the parameter parameters passed in when calling the Vue.set () method on the official website. As shown in the following:
We then looked down:
if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) }
We first look at the Isundef and Isprimitive method, from the name can be seen, isundef is to determine whether the target is equal to undefined or null. Isprimitive is the type of data that determines whether the target is a string, number, symbol, or Boolean. So here's what it means if the current environment is not a production environment and ISUNDEF (target) | | When isprimitive (target) is true, then it throws an error warning.
How the array is implemented
Then look down:
if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val }
This is actually the code that allows us to trigger the response when the array is modified, but before we parse the code we'll look at what the array in Vue actually looks like. is the array in Vue and the normal JS array, respectively:
The array we named in Vue is named Arrjs as the normal array in Arrvue,js. In fact, we usually call the normal array of push, pop, and so on is called the array prototype defined above method, we can see that Arrjs's prototype is pointing to Array.prototype, that is arrJs.__proto__ == Array.prototype
.
But in Vue's array, we find that Arrvue's prototype is not actually pointing to the array.prototype, but rather to an object (which we named this object as Arraymethods). Arraymethods above only 7 push, pop and other methods, and Arraymethods prototype is the point of Array.prototype. So we call the array of push, pop and other methods in the Vue is not actually directly called array prototype to provide us with the push, pop, and so on, but the call of the arraymethods give us the push, pop and other methods. Why does Vue want to add this arraymethods to the array's prototype chain? Here's how Vue's data response works, and we're not talking about the concrete implementation of the data response principle for the time being. Here you can understand that Vue has done special processing in the Arraymethods object, and if you invoke the 7 methods of push, pop, etc. provided by arraymethods, it will trigger the current collection of dependencies (the dependencies collected here can be temporarily interpreted as rendering functions), Causes the page to be rendered again. In other words, for the operation of the array, we only use the 7 methods provided by Arraymethods to cause page rendering, which explains why we vueInstance.$data.arr[0] = 3;
do not cause the page to render when we use it.
After figuring out exactly how the array in Vue is implemented, let's look at the code above:
if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val }
First if the current target is not an array, and the value of key is a valid array index. Then set the length of the target array to the maximum value in Target.length and key, why do you do this here? Because we might do the following:
arr1 = [1,3];Vue.set(arr1,10,1) // 如果不那样做,这种情况就会出问题
Then looking down, we found that the Target.splice (key, 1, Val) was called directly in front of us, and we said that 7 methods of push, pop, etc. that were called by Arraymethods could cause the page to be re-rendered. Just splice is also one of the 7 methods that attribute Arraymethods provides.
Summarize the Vue.set array implementation principle: In fact, Vue.set () for the array processing is actually called the splice method, is not found in fact very simple ~ ~
How objects are implemented
We then look down at the code:
if (key in target && !(key in Object.prototype)) { target[key] = val return val }
The first thing to judge is if the key is an attribute in the object, and key is not a property on the objects prototype. Note that this key is already defined on the object above, directly modify the value can be, can automatically trigger the response.
On the principle of dependency collection and triggering of objects we do not explain in detail in this article, you can understand this for the time being. Vue is used by the Object.defineproperty to the object to do a layer of interception, when the trigger is triggered by a dependency collection (the dependency collected here or as an array, as the interpretation of the rendering function), when triggering the set will trigger the dependency, causing the rendering function to perform the page re-rendering. So where is the first time to trigger get? In fact, it is triggered when the page is first loaded, and recursively the properties of the object are collected, so we have to modify the existing properties of the object to cause the page to be re-rendered. This also explains why the page is vueInstance.$data.obj1.b = 3;
not re-rendered when we use it because property B here is not an existing property of the object, which means that property B has not been collected for dependency, so it will cause the value of the property B to be modified without re-rendering.
We then look down at the code:
const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } if (!ob) { target[key] = val return val }
First, define the value of the variable ob target.__ob__
, __ob__
What is the object of this property? Vue adds a property to the response object, and __ob__
if an object has this __ob__
property, then it means that the object is a responsive object, and when we modify an object's existing properties, the page rendering is triggered.
target._isVue || (ob && ob.vmCount)
The current target object is the Vue instance object or the root data object, then an error warning is thrown.
if (!ob)
To be true, the current target object is not a responsive object, so the direct assignment returns.
Then look down:
defineReactive(ob.value, key, val) ob.dep.notify() return val
This is actually the place where Vue.set () really deals with objects. defineReactive(ob.value, key, val)
adds a dependency to the newly added attribute, and the page rendering is triggered when the new property is modified directly later.
ob.dep.notify()
The meaning of this code is to trigger the current dependency (where the dependency can still be interpreted as a render function), so the page will be re-rendered.
Summarize
See the Vue.set () and this from Vue from the source hierarchy. $set () These two APIs are still very simple, because this article does not explain the Vue dependency collection and trigger, so some places are still very vague.