先把我使用$nextTick()最多的場景放這里:
如果你在寫js/ts時要在更改完數(shù)據(jù)后,去操作DOM,那這個時候就要把操作DOM的代碼放到$nextTick()中。否則,會看到,調試的數(shù)據(jù)都是對的,但是頁面的行為怎么就是錯的。
直接查看Vue API文檔(https://vuejs.org/v2/api/#Vue-nextTick),看其對$nextTick()的定義:

這個定義的直接翻譯是:
“將回調延遲到下一個DOM更新周期之后執(zhí)行。在更改了一些數(shù)據(jù)以等待DOM更新之后立即使用它”。
作為從后端轉到前端的開發(fā),對前端的底層知識還沒深刻的理解, 第一次看到這句定義真的是摸不著頭腦:
- 什么是DOM更新周期?
- 為什么要延遲到‘下一個’DOM更新周期?
- 為什么說是“下一個”?
- 為什么要在更改了一些數(shù)據(jù)以等待DOM更新之后立即使用它?
- 什么叫“以等待DOM更新之后“?
總之是我看過最拗口的定義,無數(shù)個疑問都不知道從哪個開始突破。
那么現(xiàn)在是我第3次使用$nextTick(), 也是覺得理解對了$nextTick()的用法和目的,分享出來。
開始:
對于 $nextTick()的解釋,Vue的官方文檔放的比較零散,但是如果等通讀完文檔再著手開發(fā)項目又不現(xiàn)實。所以我摳出了截圖,我畫出 了重點,但建議你通讀截圖里的文字,這樣有助于整體理解:
其實$nextTick() API文檔下有個鏈接用于解釋“Vue的異步更新隊列”, 理解了這個就差不多理解了何時該用$nextTick() 。


翻譯過來是:Vue異步執(zhí)行DOM更新。每當觀察到數(shù)據(jù)更改時,它將打開一個隊列并緩沖同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)更改。如果同一個監(jiān)視程序被觸發(fā)多次,那么它將只被推入隊列一次。這種緩沖的重復數(shù)據(jù)刪除對于避免不必要的計算和DOM操作非常重要。然后,在下一個事件循環(huán)“tick”中,Vue刷新隊列并執(zhí)行實際的(已經被清除的)工作。在內部Vue嘗試原生承諾。然后,對異步隊列使用MutationObserver和setimmediation,并退回到setTimeout(fn, 0)。
這就解釋了前面的3個問題:
- 什么是DOM更新周期?
- 為什么要延遲到‘下一個’DOM更新周期?
- 為什么說是“下一個”?
對于一個事件的數(shù)據(jù),從“數(shù)據(jù)更改”->"緩存入數(shù)據(jù)更新隊列"->在某個條件下,將這個緩存數(shù)據(jù)被更新到DOM(頁面出現(xiàn)更新)->這個事件再監(jiān)聽數(shù)據(jù)更改。這是一個周期。也就是說,你改了數(shù)據(jù),雖然Vue是響應式的更新,有些時候你看起來好像是立刻更新了,但其實不是,Vue把改動的數(shù)據(jù)先緩存起來了,要等下一個DOM更新周期到了才會把這些數(shù)據(jù)更新到DOM里,因此,如果說你只是“看”頁面,看不出區(qū)別。如果你要在更新完數(shù)據(jù)后,去操作DOM,那這個時候就會看到,調試的數(shù)據(jù)都是對的,但是頁面的行為怎么就是錯的。 這就是我的使用場景。
下面是一個應用舉例,括號里為測試數(shù)據(jù)。如果不想看,跳過直接看總結
這里,我要做到這樣的功能,刪除當前的filter(branch-test)后,頁面要顯示默認的filter(test4).
頁面的數(shù)據(jù)結構是這樣的:
- myFilters:[]any=>所有的filter信息
- activeFilter:any=>當前被選中的filter
filter是Button, 點擊后顏色變化是通過getElementById()操作DOM 來實現(xiàn)的,正是因為這個,才需要用到$nextTick()

在下面代碼中,最后一個方法onFilterDelete 是實現(xiàn)這個功能的入口;
我說下沒有使用nextTick()看到的亂象,這樣你就能理解為什么要用nextTick():
最開始沒用$nextTick(),看到的亂象是:當我刪除了branch-test 這個filter后,橙色的字眼跳到了fontana, 而下面表格里羅列的卻是test4(default)的job, 也就是說,操作DOM得到的結果是錯的,而table數(shù)據(jù)是對的,說明數(shù)據(jù)沒有問題(當我在方法里打印出操作DOM的時候,它操作的ID也是對的)但是這里有我還沒想明白的疑問是,既然getElementById("test4")那此時舊的DOM里也有這個test4對象,為什么會渲染到fontana呢?知道的朋友也回復下
總結下:也就是說,如果你要在數(shù)據(jù)更改了之后,下一步是,需要手動(自己寫代碼)去更改DOM的內容(我把這個工作叫善后代碼段),那么,修改完數(shù)據(jù)后,善后代碼段包成一個方法,并把它放入
this.$nextTick(() => {
this.善后方法();
})
而且這3行代碼是跟在你修改數(shù)據(jù)的代碼后,像這樣,改完數(shù)據(jù)后要去修改DOM樣式:
this.myFilters.splice(index, 1); //改數(shù)據(jù)
console.log('After delete filter', this.myFilters)
this.$nextTick(() => {
this.setDefaultFilter() //修改DOM樣式
})
以下是ts代碼:
setDefaultFilter() {
let defaultFilter = this.myFilters.find(f => f.isDefault);
if (isEmpty(defaultFilter)) {
defaultFilter = this.myFilters.find(f => f.name === 'All Jobs');
}
this.activeFilter = defaultFilter
console.log('[DEBUG] set color to orange for ', filter.name);
// @ts-ignore
document.getElementById(filter.name).style.color = "#e87532e0";
for (let f of this.myFilters) {
if (f.name !== filter.name) {
// @ts-ignore
document.getElementById(f.name).style.color = "dodgerblue"
}
}
//...
}
async onFilterDelete() {
//...
let res = await service.deleteFilter(this.activeFilter.id).toPromise();
if (res.code !== 200) {
errorHandler.handle("Fail to delete Filter: " + this.activeFilter.name)
return;
}
let deleteFilterId = this.activeFilter.id;
let index = findIndex(this.myFilters, function (o) {
return ('id' in o) && o.id === deleteFilterId;
});
this.myFilters.splice(index, 1);
console.log('After delete filter', this.myFilters)
this.$nextTick(() => { //重點代碼
this.setDefaultFilter() //重點代碼
})
}