淺談Vue中的資料繫結的實現,以及Vue3.0的proxy

elvisxiao發表於2019-02-02

在開發過程中,我們時常會遇到這樣一種情況:當vue的data裡邊宣告或者已經賦值過的物件或者陣列(陣列裡邊的值是物件)時,向物件中新增新的屬性,如果更新此屬性的值,是不會更新檢視的。

根據官方文件定義:如果在例項建立之後新增新的屬性到例項上,它不會觸發檢視更新。 受現代 JavaScript 的限制 (以及廢棄 Object.observe),Vue 不能檢測到物件屬性的新增或刪除。由於 Vue 會在初始化例項時對屬性執行 getter/setter 轉化過程,所以屬性必須在 data 物件上存在才能讓 Vue 轉換它,這樣才能讓它是響應。

當然針對這種情況,官方也提供瞭解決方案,如下: Vue 不允許在已經建立的例項上動態新增新的根級響應式屬性 (root-level reactive property)。然而它可以使用Vue.set(obj, key, val)方法將響應屬性新增到巢狀的物件上: Vue.set(vm.obj, 'e', 0) 您還可以使用 vm.$set 例項方法,這也是全域性 Vue.set 方法的別名: this.$set(this.obj, 'e', 2)

那麼為什麼會這樣呢?還是要從Vue實現資料繫結的原理說起(Object.defineProperty),假設我們把Vue資料繫結精簡為下列程式碼:

<div>
    <h3>展示姓名:<span id="name"></span></h3>
    <p>輸入姓名:<input type="text" oninput="inputHandler('name', this.value)" /></p>
    <h3>展示電話號碼:<span id="phone"></span></h3>
    <p>輸入電話號碼:<input type="text" oninput="inputHandler('phone', this.value)" /></p>
</div>

<script type="text/javascript">
	// 需要監聽的物件 ----
    var obj = {
        name: null
    }

    // 定義監聽 -  Object.defineProperty的實現
    for(let key in obj) {
        let val = obj[key];
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                return val;
            },
            set(newValue) {
                if(val === newValue) {
                    return;
                }
                document.getElementById(key).innerText = newValue;
                val = newValue;
            }
        })
    }
    
    var inputHandler = function(key, value) {
        obj[key] = value;
    }
</script>
複製程式碼

那麼重點來了,我們有沒有辦法實現新增屬性也能自動繫結呢?答案當然是有,也就是即將推出的Vue3.0也採用的ES6的新API - Proxy,用新的Proxy 改些後的程式碼如下:

<div>
    <h3>展示姓名:<span id="name"></span></h3>
    <p>輸入姓名:<input type="text" oninput="inputHandler('name', this.value)" /></p>

    <h3>展示電話號碼:<span id="phone"></span></h3>
    <p>輸入電話號碼:<input type="text" oninput="inputHandler('phone', this.value)" /></p>
</div>

<script type="text/javascript">
	// 需要監聽的物件 ----
    var obj = {
        name: null
    }

    obj = new Proxy(obj, {
        get: function(target, prop) {
            console.log('proxy get:', target, prop);
        },

        set: function(target, prop, value) {
            document.getElementById(prop).innerText = value;
            target[prop] = value;
            console.log('proxy:', target, prop, value);
        }
    })
    
    var inputHandler = function(key, value) {
        obj[key] = value;
    }
</script>
複製程式碼

執行起來看看,是不是不使用$set,就能實現新增屬性的繫結呢?期待Vue3.0的到來。

相關文章