原文連接:https://ssshooter.com/2019-09...html
默認此文讀者明白簡單的 Vue 底層原理,對此陌生的讀者能夠先看:vue
此文使用的 Vue 版本是 2.0+,在線例子看這裏,下面順便也把關鍵代碼貼出來。react
<template> <div class="hello"> <button @click="inputvalue.aaaa = 'aaaa is here'">show aaaa</button> <button @click="$forceUpdate()">forceupdate</button> {{inputvalue.aaaa}} <br /> cccc {{inputvalue.cccc}} <input v-model="inputvalue.cccc" placeholder="with v-model" /> <input @input="inputvalue.cccc = $event.target.value" :value="inputvalue.cccc" placeholder="with @input" /> <br /> bbbb {{inputvalue.bbbb}} <input v-model="inputvalue.bbbb" placeholder="with v-model" /> <input @input="inputvalue.bbbb = $event.target.value" :value="inputvalue.bbbb" placeholder="with @input" /> </div> </template> <script> export default { data() { return { inputvalue: { bbbb: '', }, } }, } </script>
最近的項目大量接觸到動態新增的數據,以爲必需要搞清楚到底何時 vue 會讓視圖更新,視圖修改數據又會不會反映到數據模型。git
因而寫了簡單幾個例子做爲對比,結合一年前研究了一下可是如今忘得差很少的 Vue 原理知識,解決了這麼個問題 ——github
什麼狀況下動態添加對象屬性是安全操做(換句話說就是能夠保證數據是響應式的)?api
首先解釋例子中 inputvalue.aaaa
不顯示的問題。數組
這要從 Vue 的響應式原理提及。在初始化的時候 Vue 會把 data 的數據遞歸掃描一遍,設置 setter 和 getter。安全
getter 的做用是在數據被讀取時記下當前的調用者,這個調用者也就是這個數據的「訂閱者」。ssh
若視圖使用了某個數據,處理頁面時就會調用該數據,成爲該數據的一個訂閱者。函數
setter 的做用是在數據被賦值時,會提醒他的訂閱者該數據已更新,而後訂閱者就知道要運行對應的更新操做,例如視圖更新、watch 函數。
設置 getter,setter 常被稱爲劫持,感受也挺形象的,下面就簡單用劫持指代這個行爲。
既然在初始化時數據才被劫持,那麼你忽然的定義 this.inputvalue.aaaa = 'aaaa is here'
顯然會讓 Vue 猝不及防。這個屬性即便有訂閱者,可是由於沒有走到「劫持」這一步,因此這個屬性根本意識不到他有訂閱者。
其實把數據打印出來能夠簡單地斷定這個數據是否已經被劫持。以下圖,bb 沒有被劫持,aa、cc 都已被劫持。
最簡單的方法是:直接在 data 寫清楚,也就是頁面用了什麼屬性都必須寫上。例如對於 inputvalue.aaaa
,就直接在 data 裏面加上 aaaa 屬性。
可是...想了想,這大概不算「動態」添加了吧 😂
向響應式對象中添加一個屬性,並確保這個新屬性一樣是響應式的,且觸發視圖更新。它必須用於向響應式對象上添加新屬性,由於 Vue 沒法探測普通的新增屬性 (好比 this.myObject.newProperty = 'hi')
Vue.set
或者 Vue 實例的 $set
都是同樣的,總之就是手動觸發一次劫持,以後在更新的時候就能觸發視圖從新渲染啦!
不過,其實 set 在一種狀況下會失效,這個後面會提到...
這個方法算是一種曲線救國吧。
若是你不須要雙向綁定,在動態新增屬性時你可使用 $forceUpdate()
。這個函數的做用就如其名,強制更新從新渲染。
上面說過了,雖然你設置新數據沒有通知頁面從新渲染,不過數據終究是改了。因此你只須要強制更新視圖,就能看到數據修改後的效果。
<input :value="myValue.property" @input="myValue.property = $event.target.value" />
你可能從未聽過 Vue 單向綁定,可是這樣作也算是一個單向綁定了 😂
當你的輸入確實地改變了 myValue.property
的值,可是不會觸發任何關於 myValue.property
的更新。真的須要更新的時候 forceUpdate
就能夠了。
若是數組裏有對象,只要單個對象符合上面操做便可,沒有特別須要注意的地方。
可是老調重彈,數組更新方法仍是須要注意,你能夠經過整個數組從新賦值以及 push()、pop()、shift()、unshift()、splice()、sort()、reverse() 這幾個通過包裹的方法觸發更新。
對比上面 cccc
的兩個輸入框:
<input v-model="inputvalue.cccc" placeholder="with v-model" /> <input @input="inputvalue.cccc = $event.target.value" :value="inputvalue.cccc" placeholder="with @input" />
進行兩種操做:
操做一,一切正常;操做二,沒法更新。這就證實坊間流傳的 v-model 是 @input 和 :value 的語法糖這個說法至少放在如今確定是錯的(其實我往下試了幾個版本,這兩個操做表現都是不一致的,以爲很迷惑,可是這不是重點,先不糾結了)。
在 stackoverflow 上通過大佬指點,上面的狀況其實很容易理解,形成這個區別的重點有兩個:
因此對於操做一,v-model 幫你把數據 set 了,天然一切正常;操做二,@input 先把屬性直接靜態添加了,到了 v-model 的時候 set 不會再劫持已經存在的屬性。
這就引出了一個須要注意的地方,如果先直接賦值,即便再用 set 也不能再劫持這個屬性了,這個可憐弱小又的屬性已經沒法再變成響應式了。