原文連結:做棵大樹
在最近參與的一個專案中,前端用到了vue.js 框架,期間有個功能需要動態的向一個被綁定的物件中新增屬性。但在實際應用中問題出現了:在物件中新增屬性後,與物件綁定的元件內容卻未發生變化,必須再次刷新元件,其內容才會變成變更後的內容。
起初我以為是屬性沒有加入成功,因為在我的印像中 v-model
是雙向綁定的,不會出現不更新的狀態。在我查看 Devtools 中的監控後,發現對應的物件確實添加了指定的屬性。
於是,我前去查看了官方文檔,找到了官方給出的解釋:Vue.js如何追蹤變化
##當你把一個普通的JavaScript 物件傳入Vue 實例作為
data
選項,Vue 將遍歷此物件所有的property,並使用Object.defineProperty
把這些property 全部轉為getter/setter。Object.defineProperty
是 ES5 中一個無法 shim 的特性,這也就是 Vue 不支援 IE8 以及更低版本瀏覽器的原因。每個元件實例都對應一個 watcher 實例,它會在元件渲染的過程中把「接觸」過的資料 property 記錄為依賴。 之後當依賴項的 setter 觸發時,會通知 watcher,從而使它關聯的元件重新渲染。
#由於JavaScript 的限制,Vue 無法偵測陣列和物件的變化。儘管如此我們還是有一些辦法來迴避這些限制並保證它們的回應性。
#Vue 無法偵測 property 的新增或移除。由於 Vue 會在初始化實例時對 property 執行 getter/setter 轉化,所以 property 必須在 data
物件上存在才能讓 Vue 將它轉換為響應式的。例如:
<span style="display: block; background: url(https://imgkr.cn-bj.ufileos.com/97e4eed2-a992-4976-acf0-ccb6fb34d308.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #1E1E1E; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #DCDCDC; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #1E1E1E; border-radius: 5px;">var vm = new Vue({<br/> data:{<br/> a:1<br/> }<br/>})<br/><br/>// `vm.a` 是响应式的<br/><br/>vm.b = 2<br/>// `vm.b` 是非响应式的<br/></code>
對於已經建立的實例,Vue 不允許動態新增根層級的回應式 property。但是,可以使用 Vue.set(object, propertyName, value)
方法為嵌套物件新增響應式 property。例如,對於:
<span style="display: block; background: url(https://imgkr.cn-bj.ufileos.com/97e4eed2-a992-4976-acf0-ccb6fb34d308.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #1E1E1E; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #DCDCDC; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #1E1E1E; border-radius: 5px;">Vue.set(vm.someObject, <span class="hljs-string" style="color: #D69D85; line-height: 26px;">'b'</span>, 2)<br/></code>
您也可以使用vm.$set
實例方法,這也是全域Vue.set
方法的別名:
<span style="display: block; background: url(https://imgkr.cn-bj.ufileos.com/97e4eed2-a992-4976-acf0-ccb6fb34d308.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #1E1E1E; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #DCDCDC; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #1E1E1E; border-radius: 5px;">this.<span class="hljs-variable" style="color: #BD63C5; line-height: 26px;">$set</span>(this.someObject,<span class="hljs-string" style="color: #D69D85; line-height: 26px;">'b'</span>,2)<br/></code>
有時你可能需要為已有物件賦值多個新property,例如使用Object.assign()
或_.extend()
。但是,這樣新增到物件上的新 property 不會觸發更新。在這種情況下,你應該用原始物件與要混合進去的物件的 property 一起建立一個新的物件。
<span style="display: block; background: url(https://imgkr.cn-bj.ufileos.com/97e4eed2-a992-4976-acf0-ccb6fb34d308.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #1E1E1E; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #DCDCDC; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #1E1E1E; border-radius: 5px;">// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`<br/>this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })<br/></code>
這是對於物件賦值的解決方式,在採用了官方的解決方案 this.$set(object, key, value)
後確實實現了即時更新的效果。同時對於陣列等情況,可查看 餘下官方文件
如官方所說「由於JavaScript 的限制,Vue 無法偵測陣列和物件的變化。」 ,但為什麼會這樣呢?
借用 Segmentfault UKer 的回答:
ECMAScript中有两种属性:数据属性 和 访问器属性; 数据属性的描述符为:Configurable,Enumerable,Writable,Value; 访问器属性的描述符为:Configurable, Enumerable,set,get。
当我们使用new Vue(obj)
,其内部发生了大体如下代码的转换,即,将数据属性转换为了访问器属性
<span style="display: block; background: url(https://imgkr.cn-bj.ufileos.com/97e4eed2-a992-4976-acf0-ccb6fb34d308.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #1E1E1E; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #DCDCDC; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #1E1E1E; border-radius: 5px;"><span class="hljs-keyword" style="color: #569CD6; line-height: 26px;">function</span> Vue(obj){<br/> obj.data.keys().forEach((prop, index) => {<br/> Object.defineProperty(obj.data, prop, {<br/> <span class="hljs-function" style="color: #DCDCDC; line-height: 26px;"><span class="hljs-title" style="color: #DCDCDC; line-height: 26px;">set</span></span>(){<br/> //可以在此处进行事件监听<br/> },<br/> <span class="hljs-function" style="color: #DCDCDC; line-height: 26px;"><span class="hljs-title" style="color: #DCDCDC; line-height: 26px;">get</span></span>(){<br/> <br/> }<br/> })<br/> })<br/> <span class="hljs-built_in" style="color: #4EC9B0; line-height: 26px;">return</span> obj;<br/> }<br/></code>
但是当我们后面再次使用普通的赋值,仅仅是赋值了一个数据属性的,这个属性是不会具有访问器属性的事件监听功能的。
至此,v-model
绑定数据不实时更新的问题方才得到了解决。
以上是[Vue] v-model 綁定物件不即時更新的詳細內容。更多資訊請關注PHP中文網其他相關文章!