最近在vue項目中使用了一個複雜對象,發現改變該對象的屬性值,頁面視圖未發生改變,查了一下,發現這個問題你們仍是有遇到過的,記錄一下解決方案。vue
問題:vue數據結構有多層,改變二級結構數據,dom節點沒有從新渲染?????ajax
數組
數據結構
1.DOM更新循環是指什麼?app
2.下次更新循環是何時?框架
3.修改數據以後使用,是加快了數據更新進度嗎?dom
4.在什麼狀況下要用到?異步
ide
在vue的文檔中,說明vue是異步執行DOM更新的。函數
具體來講,異步執行的運行機制以下。
全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。
主線程以外,還存在一個任務隊列(task queue)。只要異步任務有了運行結果,就在「任務隊列」之中放置一個時間。
一旦「執行棧」中的全部同步任務執行玩不,系統就會讀取「任務隊列」,看看裏面有哪些事件。哪些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。
主線程不斷重複上面的第三步。
下圖就是主線程和任務隊列的示意圖。
1 //改變數據 2 vm.message = 'changed' 3 //想要當即使用更新後的DOM,這樣是不行的,由於設置的message後DOM尚未更新 4 console.log(vm.$el.textContent) //並無獲得改變後的changed 5 //使用nextTick()這樣就能夠了,nextTick()裏面的代碼會再DOM更新後執行 6 Vue.nextTick(function(){ 7 console.log(vm.$el.textContent) //能夠獲得changed 8 })
第一個tick(圖例中第一個步驟,即‘本次更新循環’):
首先修改數據,這是同步任務。同一事件循環的全部同步任務都在主線程上執行,造成一個執行棧,此時還未涉及到DOM
vue開啓任務隊列,並緩衝在此事件中繁盛的全部數據改變。若是同一個watcher被屢次觸發,只會被推入到隊列中一次
第二個tick(圖例中第二個步驟,即‘下次更新循環’)
同步任務執行完畢,開始執行異步watcher隊列中的任務,更新DOM。Vue在內部嘗試對異步隊列使用原生的Promise.then 和MessageChannel方法,若是執行環境不支持,會採用setTimeout(fn,0)代替。
第三個tick(圖例中第三個步驟)
此時就是文檔中所說的下次DOM更新循環結束以後
。
Vue.$nextTick()
獲取的DOM更新後的數據 ,Vue.nextTick()
數據更新後的數據。
此時經過Vue.nextTick(獲取到改變後的DOM).經過setTimeout(fn,0)也能夠獲取到。
簡單總結事件循環:
同步代碼執行-->查找異步隊列,推入執行棧,執行Vue.nextTick[事件循環1] -->查找異步隊列,推入執行棧,執行Vue.nextTick[事件循環2] ...
總之,異步是單獨的一個tick,不會和同步在tick裏發生,也是DOM不會立刻變化的緣由。
應用場景:須要在視圖更新以後,基於新的視圖進行操做
注意 mounted 不會承諾全部的子組件也都一塊兒被掛載。若是你但願等到整個視圖都渲染完畢,能夠用 vm.$nextTick 替換掉 mounted.
1 mounted: function () { 2 this.$nextTick(function () { 3 // Code that will run only after the 4 // entire view has been rendered 5 }) 6 }
eg1:
點擊按鈕顯示本來以 v-show = false 隱藏起來的輸入框,並獲取焦點。
1 showsou(){ 2 this.showit = true //修改 v-show 3 document.getElementById("keywords").focus() //在第一個 tick 裏,獲取不到輸入框,天然也獲取不到焦點 4 }
修改成:
1 showsou(){ 2 this.showit = true 3 this.$nextTick(function () { 4 // DOM 更新了 5 document.getElementById("keywords").focus() 6 }) 7 }
點擊獲取元素寬度。
1 <div id="app"> 2 <p ref="myWidth" v-if="showMe">{{ message }}</p> 3 <button @click="getMyWidth">獲取p元素寬度</button> 4 </div> 5 6 getMyWidth() { 7 this.showMe = true; 8 //this.message = this.$refs.myWidth.offsetWidth; 9 //報錯 TypeError: this.$refs.myWidth is undefined 10 this.$nextTick(()=>{ 11 //dom元素更新後執行,此時能拿到p元素的屬性 12 this.message = this.$refs.myWidth.offsetWidth; 13 }) 14 }
使用 swiper 插件經過 ajax 請求圖片後的滑動問題。
$.each(data.resultData,function (index,item) { item.showChild = false; self.tableData.push(item) })
1 //this.$nextTick()不能監聽到底層的數據變化 2 showDetail(item){ 3 console.log(this.tableData) 4 console.log(item) 5 this.$nextTick(function () { 6 item.showChild = !item.showChild 7 }) 8 },
因此在此用深度監聽:
1 watch:{ 2 tableData:{ 3 handler:function(val,oldVal){ 4 this.tableData = val 5 }, 6 //深度監聽 7 deep:true 8 } 9 }
1 // bad 2 created() { 3 this.fetchUserList(); 4 }, 5 watch: { 6 searchText: 'fetchUserList', 7 }
// good watch: { searchText: { handler: 'fetchUserList', immediate: true, } }
有時數據層次太多,視圖沒有自動更新,需手動強制刷新。添加this.$forceUpdate()
進行強制渲染,效果實現。