Do you want to add new attributes to objects in Vue without refreshing the interface?

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

Added by aromakat on Sun, 24 Oct 2021 05:34:47 +0300