1, The problem of adding attributes directly
Let's start with an example
Define a p tag and traverse it through the v-for instruction
Then bind the click event to the botton tag. We expect that when we click the button, a new attribute will be added to the data and a new line will be added to the interface
<p v-for="(value,key) in item" :key="key"> {{ value }} </p> <button @click="addProperty">Add new attributes dynamically</button>
Instantiate a vue instance and define the data attribute and methods method
const app = new Vue({ el:"#app", data:()=>{ item:{ oldProperty:"Old attribute" } }, methods:{ addProperty(){ this.items.newProperty = "new property" // Add a new property for items console.log(this.items) // Output items with newProperty } } })
Click the button and find that the result is not as expected. Although the data is updated (the console prints out new properties), the page is not updated
2, Principle analysis
Why did this happen?
Let's analyze it
vue2 has used Object.defineProperty to implement data response
const obj = {} Object.defineProperty(obj, 'foo', { get() { console.log(`get foo:${val}`); return val }, set(newVal) { if (newVal !== val) { console.log(`set foo:${newVal}`); val = newVal } } }) }
Setters and getter s can be triggered when we access the foo property or set the foo value
obj.foo obj.foo = 'new'
However, when we add new attributes to obj, we cannot trigger the interception of event attributes
obj.bar = 'new property'
The reason is that at the beginning, the foo property of obj is set as responsive data, while bar is a new property added later, and it is not set as responsive data through Object.defineProperty
3, Solution
Vue Dynamic addition of new responsive properties on an instance that has already been created is not allowed
If you want to update data and view synchronously, you can adopt the following three solutions:
-
Vue.set()
-
Object.assign()
-
$forcecUpdated()
Vue.set()
Vue.set( target, propertyName/index, value )
parameter
-
{Object | Array} target
-
{string | number} propertyName/index
-
{any} value
Return value: the set value
Add a property to the responsive object through Vue.set and ensure that the new property Property is also responsive and triggers view updates
About Vue.set source code (omitting a lot of code not related to this section)
Source location: src\core\observer\index.js
function set (target: Array<any> | Object, key: any, val: any): any { ... defineReactive(ob.value, key, val) ob.dep.notify() return val }
Here is nothing more than calling the defineReactive method again to implement the response of the new attribute
For the defineReactive method, property interception is implemented internally through Object.defineProperty
The approximate code is as follows:
function defineReactive(obj, key, val) { Object.defineProperty(obj, key, { get() { console.log(`get ${key}:${val}`); return val }, set(newVal) { if (newVal !== val) { console.log(`set ${key}:${newVal}`); val = newVal } } }) }
Object.assign()
A new attribute added to an object directly using Object.assign() does not trigger an update
You should create a new object and merge the properties of the original object and the mixed object
this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})
$forceUpdate
If you find yourself in need In a forced update in Vue, 99.9% of the cases are that you did something wrong somewhere
$forceUpdate Vue Instance re rendering
PS: only sub components that affect the instance itself and the contents of the insertion slot, not all sub components.
Summary
-
If you add a small number of new attributes to an object, you can directly adopt Vue.set()
-
If you need to add a large number of new attributes to a new object, create a new object through Object.assign()
-
If you need a forced refresh, you can take $forceUpdate() (not recommended)
PS: vue3 uses proxy to realize data response. Adding new attributes directly and dynamically can still realize data response