Vue.nextTick 的原理和用途


mounted: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been rendered }) }

概覽

官方文檔說明:
  • 用法:

在下次 DOM 更新循環結束之后執行延遲回調。在修改數據之后立即使用這個方法,獲取更新后的 DOM。

疑問:
  1. DOM 更新循環是指什么?
  2. 下次更新循環是什么時候?
  3. 修改數據之后使用,是加快了數據更新進度嗎?
  4. 在什么情況下要用到?

原理

異步說明

Vue 實現響應式並 不是數據發生變化之后 DOM 立即變化,而是按一定的策略進行 DOM 的更新。

在 Vue 的文檔中,說明 Vue 是異步執行 DOM 更新的。關於異步的解析,可以查看阮一峰老師的這篇文章。截取關鍵部分如下:

具體來說,異步執行的運行機制如下。

(1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
(2)主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重復上面的第三步。

下圖就是主線程和任務隊列的示意圖。

clipboard.png

事件循環說明

簡單來說,Vue 在修改數據后,視圖不會立刻更新,而是等同一事件循環中的所有數據變化完成之后,再統一進行視圖更新。

知乎上的例子:

//改變數據
vm.message = 'changed'

//想要立即使用更新后的DOM。這樣不行,因為設置message后DOM還沒有更新
console.log(vm.$el.textContent) // 並不會得到'changed'

//這樣可以,nextTick里面的代碼會在DOM更新后執行
Vue.nextTick(function(){
    console.log(vm.$el.textContent) //可以得到'changed'
})

圖解:

clipboard.png

事件循環:

第一個 tick(圖例中第一個步驟,即'本次更新循環'):

  1. 首先修改數據,這是同步任務。同一事件循環的所有的同步任務都在主線程上執行,形成一個執行棧,此時還未涉及 DOM 。
  2. Vue 開啟一個異步隊列,並緩沖在此事件循環中發生的所有數據改變。如果同一個 watcher 被多次觸發,只會被推入到隊列中一次。

第二個 tick(圖例中第二個步驟,即'下次更新循環'):

同步任務執行完畢,開始執行異步 watcher 隊列的任務,更新 DOM 。Vue 在內部嘗試對異步隊列使用原生的 Promise.then 和 MessageChannel 方法,如果執行環境不支持,會采用 setTimeout(fn, 0) 代替。

第三個 tick(圖例中第三個步驟):

此時就是文檔所說的

下次 DOM 更新循環結束之后

此時通過 Vue.nextTick 獲取到改變后的 DOM 。通過 setTimeout(fn, 0) 也可以同樣獲取到。


簡單總結事件循環:

同步代碼執行 -> 查找異步隊列,推入執行棧,執行Vue.nextTick[事件循環1] ->查找異步隊列,推入執行棧,執行Vue.nextTick[事件循環2]...

總之,異步是單獨的一個tick,不會和同步在一個 tick 里發生,也是 DOM 不會馬上改變的原因。

對於事件循環,可以在這里查看更詳細的內容:  https://segmentfault.com/a/11...

用途

應用場景:需要在視圖更新之后,基於新的視圖進行操作。

created、mounted

需要注意的是,在 created 和 mounted 階段,如果需要操作渲染后的試圖,也要使用 nextTick 方法。

官方文檔說明:

注意 mounted 不會承諾所有的子組件也都一起被掛載。如果你希望等到整個視圖都渲染完畢,可以用 vm.$nextTick 替換掉 mounted

其他應用場景

其他應用場景如下三例:

例子1:

點擊按鈕顯示原本以 v-show = false 隱藏起來的輸入框,並獲取焦點。

showsou(){
  this.showit = true //修改 v-show
  document.getElementById("keywords").focus()  //在第一個 tick 里,獲取不到輸入框,自然也獲取不到焦點
}

例子2:

點擊獲取元素寬度。

<div id="app">
    <p ref="myWidth" v-if="showMe">{{ message }}</p>
    <button @click="getMyWidth">獲取p元素寬度</button>
</div>

getMyWidth() {
    this.showMe = true;
    //this.message = this.$refs.myWidth.offsetWidth;
    //報錯 TypeError: this.$refs.myWidth is undefined
    this.$nextTick(()=>{
        //dom元素更新后執行,此時能拿到p元素的屬性
        this.message = this.$refs.myWidth.offsetWidth;
  })
}

例子3:

使用 swiper 插件通過 ajax 請求圖片后的滑動問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM