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
值的時候都能夠觸發setter
與getter
obj.foo
obj.foo = 'new'
但是我們為obj
新增新屬性的時候,卻無法觸發事件屬性的攔截
obj.bar = '新屬性'
原因是一開始obj
的foo
屬性被設成了響應式資料,而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
實現資料響應式的,直接動態新增新屬性仍可以實現資料響應式