Vue - 給對象新增屬性(使用Vue.$set())------解決vue數據已經改變,視圖不刷新問題

今天做項目的過程中遇到一個問題,百思不得其解,詢問大佬後恍然大悟,踩坑的主要原因是知識盲區,上網搜到一篇比較好的簡書博客,轉載過來,並加上自己的一些見解,希望能幫到大家,原文鏈接:Vue - 給對象新增屬性(使用Vue.$set())。

1、Vue 的數據不是響應式嗎?

在開發過程中,我們可能會遇到這樣一種情況:當 vue 的 data 裏邊聲明或者已經賦值過的對象或者數組(數組裏邊的值是對象)時,向對象中添加新的屬性,如果更新此屬性的值,是不會更新視圖的。

根據官方文檔定義:如果在實例創建之後添加新的屬性到實例上,它不會觸發視圖更新。

當你把一個普通的 JavaScript 對象傳入 Vue 實例作爲 data 選項,Vue 將遍歷此對象所有的屬性,並使用 Object.defineProperty() 把這些屬性全部轉爲 getter/setter,從而實現對 data 的監聽,從而實現一旦 data 改變就刷新視圖的效果。

受現代 JavaScript 的限制 (以及廢棄 Object.observe),Vue 不能檢測到對象屬性的添加或刪除。由於 Vue 會在初始化實例時對屬性執行 getter/setter 轉化過程,所以屬性必須在 data 對象上存在才能讓 Vue 轉換它,這樣才能讓它是響應的。

看以下實例:

<template>
    <div>
        <p @click="addd(obj)">{{obj.d}}</p>
        <p @click="adde(obj)"> {{obj.e}}</p>
    </div>
</template>
<script>
export default {
    data(){
        return {
            obj:{}
        }
    },
    mounted() {
        this.obj = {d: 0};
        this.obj.e = 0;
        console.log('after--', this.obj);
    },
    methods: {
        addd(item) {
            item.d = item.d + 1;
            console.log('item--',item);
        },
        adde(item) {
            item.e = item.e + 1;
            console.log('item--',item);
        }
    }
}

在這裏插入圖片描述
可以看出 d 屬性是有 get 和 set 方法的,而新增的 e 屬性是沒有的,這個 e 就是 data 新增的屬性,Vue 是不會監聽它的。

點擊觸發 3 次 addd 事件,點擊觸發 3 次 adde 事件,頁面效果及控制檯信息如下
在這裏插入圖片描述
此時再觸發 1 次 addd 事件,頁面效果如下:
在這裏插入圖片描述
由此可以看出,更新新增屬性 e,是不會更新視圖,但是會改變其值,當更新原有屬性 d 時,會更新視圖,同時將新增的屬性 e 的值也更新到視圖裏邊。

2、如何解決上述的問題

官方定義:Vue 不允許在已經創建的實例上動態添加新的根級響應式屬性 (root-level reactive property)。然而它可以使用 Vue.set(object, key, value) 或 this.$set(object, key, value) 方法將響應屬性添加到嵌套的對象上:

Vue.set(vm.obj, ‘e’, 0); //你還可以使用 vm.$set 實例方法,這也是全局 Vue.set 方法的別名:

this.$set(this.obj,‘e’,02);
有時你想向已有對象上添加一些屬性,例如使用 Object.assign() 或 _.extend() 方法來添加屬性。但是,添加到對象上的新屬性不會觸發更新。在這種情況下可以創建一個新的對象,讓它包含原對象的屬性和新的屬性:

// 代替 Object.assign(this.obj, { a: 1, e: 2 })
this.obj = Object.assign({}, this.obj, { a: 1, e: 2 })
上述實例解決如下:
在這裏插入圖片描述
點擊觸發 3 次 addd 事件,點擊觸發 3 次 adde事件,頁面效果及控制檯信息如下:
在這裏插入圖片描述

3、一些自己的見解

博客一開始講到了,實例創建之後添加新的屬性到實例上,它不會觸發視圖更新。之所以會這樣,要涉及到 Vue 的數據響應式原理,Vue 通過 Object.defineProperty( vm,‘args’,{ value:default } );實現對數據 args 的監聽,一旦 vm 監聽到 args 數據的改變就會刷新視圖,但是如果是後來追加的屬性,則是不會調用 Object.defineProperty() 對新屬性進行監聽的,如果新屬性改變了,雖然 args 在內存中的值會改變,但是 vm 實例是監聽不到 args 的,所以不會調用 render() 函數對視圖進行刷新。

如果想讓視圖刷新,可以通過修改 vm 上的已經被監聽的數據,數據改變後就會被 vm 監聽到,然後調用 render() 函數進行視圖刷新,這樣就會順帶將未被監聽的屬性一併渲染到頁面上,當然這種方法是不推薦的,這也就是我踩得坑,控制檯能打印出未被監聽數據的改變,但是頁面沒刷新,是顯示的原來的值,差點沒坑死我!

Vue 通過 Object.defineProperty() 的方式實現對數據的監聽和響應式,導致的後來新增的屬性不會被監聽到!

針對上述問題,Vue 官方推薦的 Vue.set(object, key, value) 或 this.set(object,key,value)Vue.set()vm.set(object, key, value) 的方式( Vue.set()、vm.set()、this.$set() 三者其實是一個東西),能夠實現對新增屬性的監聽,那該方法的底層到底做了什麼呢?

新增 key 屬性,但是這個屬性並沒有被 Vue 監聽。
Vue 對新增的屬性自動創建代理和監聽(如果沒有創建過)。
新增屬性創建後,會觸發 Vue 進行視圖更新(但並不會立刻更新)。
避免這個坑的要領有兩中方式,第一種:在 Vue 實例創建之前就將所有的屬性聲明好(即使沒有默認值,但是後續操作會用到該屬性)。第二種:在實例創建後,如果需要追加新屬性可以通過 Vue 官方提供的 Vue.set() 方法,保證新屬性的數據響應式。

注意:如果實例中的數據是數組的話,因爲數組中的元素及長度可能不確定,沒有辦法在實例創建前完全聲明好,所以只能用 Vue.set() 的方式來對數組中相關的新屬性進行操作。理論上是這樣(this.$set 作用於數組時,並不會自動添加監聽和代理),但是 Vue 針對這一問題將數組數據進行看加工,可以實現對數組的直接改變,無需進行上述兩種避坑操作就可實現數組數據的響應式:就是在數組的原型之前加一層原型,這層原型涵蓋了幾個 變異 後的數組 API,具體有:pop()、push()、shift()、unshift()、sort()、splice()、reverse() 七個方法,這幾個方法數組原型上的方法同名,但是它內部會有 Vue.set() 的操作,這樣開發人員在操作數組數據時,就不需要擔心新增的數組內屬性不會被視圖所監聽了。

附上 Vue 官方文檔關於數組變異方法的說明:https://cn.vuejs.org/v2/guide/list.html#bsa-native

原文地址: https://blog.csdn.net/Marker__/article/details/105138132

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章