1. 程式人生 > 其它 >Vue2.x中給物件新增新屬性介面不重新整理?

Vue2.x中給物件新增新屬性介面不重新整理?

一、直接新增屬性的問題

我們從一個例子開始

定義一個p標籤,通過v-for指令進行遍歷

然後給botton標籤繫結點選事件,我們預期點選按鈕時,資料新增一個屬性,介面也新增一行

<p v-for="(value,key) in item" :key="key">
    {{ value }}
</p>
<button @click="addProperty">動態新增新屬性</button>

例項化一個vue例項,定義data屬性和methods方法

const app = new Vue({
    el:"#app",
    data:()=>{
        item:{
            oldProperty:"舊屬性"
        }
    },
    methods:{
        addProperty(){
            this.items.newProperty = "新屬性"  // 為items新增新屬性
            console.log(this.items)  // 輸出帶有newProperty的items
        }
    }
})

點選按鈕,發現結果不及預期,資料雖然更新了(console打印出了新屬性),但頁面並沒有更新

二、原理分析

為什麼產生上面的情況呢?

下面來分析一下

vue2是用過Object.defineProperty實現資料響應式

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
            }
        }
    })
}

當我們訪問foo屬性或者設定foo值的時候都能夠觸發settergetter

obj.foo   
obj.foo = 'new'

但是我們為obj新增新屬性的時候,卻無法觸發事件屬性的攔截

obj.bar  = '新屬性'

原因是一開始objfoo屬性被設成了響應式資料,而bar是後面新增的屬性,並沒有通過Object.defineProperty設定成響應式資料

三、解決方案

Vue 不允許在已經建立的例項上動態新增新的響應式屬性

若想實現資料與檢視同步更新,可採取下面三種解決方案:

  • Vue.set()
  • Object.assign()
  • $forcecUpdated()

Vue.set()

Vue.set( target, propertyName/index, value )

引數

  • {Object | Array} target
  • {string | number} propertyName/index
  • {any} value

返回值:設定的值

通過Vue.set向響應式物件中新增一個property,並確保這個新 property同樣是響應式的,且觸發檢視更新

關於Vue.set原始碼(省略了很多與本節不相關的程式碼)

原始碼位置: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
}

這裡無非再次呼叫defineReactive方法,實現新增屬性的響應式

關於defineReactive方法,內部還是通過Object.defineProperty實現屬性攔截

大致程式碼如下:

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()

直接使用Object.assign()新增到物件的新屬性不會觸發更新

應建立一個新的物件,合併原物件和混入物件的屬性

this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})

$forceUpdate

如果你發現你自己需要在 Vue中做一次強制更新,99.9% 的情況,是你在某個地方做錯了事

$forceUpdate迫使Vue 例項重新渲染

PS:僅僅影響例項本身和插入插槽內容的子元件,而不是所有子元件。

小結

  • 如果為物件新增少量的新屬性,可以直接採用Vue.set()

  • 如果需要為新物件新增大量的新屬性,則通過Object.assign()建立新物件

  • 如果你需要進行強制重新整理時,可採取$forceUpdate() (不建議)

PS:vue3是用過proxy實現資料響應式的,直接動態新增新屬性仍可以實現資料響應式