Vue的nextTick是什么?


公司做之前項目的時候,遇到了一些比較困惑的問題,后來研究明白了nextTick的用法。

我們先看兩種情況:

第一種:

export default {
  data () {
    return {
      msg: 0
    }
  },
  mounted () {
    this.msg = 1
    this.msg = 2
    this.msg = 3
  },
  watch: {
    msg () {
      console.log(this.msg)
    }
  }
}

這段腳本執行我們猜測會依次打印:1、2、3。但是實際效果中,只會輸出一次:3。為什么會出現這樣的情況?

原因:

當觸發update更新的時候,會去執行queueWatcher方法,也就是說,下一個循環開始時調用,此時msg已經變成3了。

保證更新視圖操作DOM的動作是在當前棧執行完以后下一個Tick(或者是當前Tick的微任務階段)的時候調用,大大優化了性能。

第二種情況:

<body>
    <div id="main">
        <ul class="list">
            <li class="item" v-for="item in list">{{ item }}</li>
        </ul>
    </div>
    
    <script>
        new Vue({
            el: '#main',
            data: {
                list: [
                    'AAAAAAAAAA',
                    'BBBBBBBBBB',
                    'CCCCCCCCCC'
                ]
            },
            mounted: function () {
                this.list.push('DDDDD')
            }
        })
    </script>
</body>

隨便給了點樣式之后,頁面是這樣的:

 

 看起來似乎一切正常,我們在給數組添加了一條數據之后,頁面也確實對應的更新了。可是,當我們在打印這個 ul 元素里 li 的 length 時,問題出現了:

    mounted: function () {
        this.list.push('DDDDD')
        console.log(this.$el.querySelectorAll('.item').length)  // 3
    }

這時候如果我們有需求需要通過 li 的個數來計算出 ul 容器的高度來進行布局,顯然就有問題了。

而這時候 Vue 的 nextTick 就可以幫助我們解決這個問題:

 

    mounted: function () {
        this.list.push('DDDDD')
        Vue.nextTick(function() {
            console.log(this.$el.querySelectorAll('.item').length)  // 4
            // ... 計算
        })

關於 Vue 的異步更新隊列,官網是這么說的:
當你設置 vm.someData = 'new value' ,該組件不會立即重新渲染。當刷新隊列時,組件會在事件循環隊列清空時的下一個“tick”更新。多數情況我們不需要關心這個過程,但是如果你想在 DOM 狀態更新后做點什么,這就可能會有些棘手。雖然 Vue.js 通常鼓勵開發人員沿着“數據驅動”的方式思考,避免直接接觸 DOM,但是有時我們確實要這么做。為了在數據變化之后等待 Vue 完成更新 DOM ,可以在數據變化之后立即使用 Vue.nextTick(callback) 。這樣回調函數在 DOM 更新完成后就會調用。
 
簡單說,因為 DOM 至少會在當前線程里面的代碼全部執行完畢再更新。所以不可能做到在修改數據后並且 DOM 更新后再執行,要保證在 DOM 更新以后再執行某一塊代碼,就必須把這塊代碼放到下一次事件循環里面,比如 setTimeout(fn, 0),這樣 DOM 更新后,就會立即執行這塊代碼。

js 是單線程語言

我們都知道,js 執行的所有任務都需要排隊,一個任務必須要等它前面的一個任務執行完之后才能執行。如果前一個任務需要花費大量的時間來計算,那么后一個任務就必須一直等它執行完才會輪到它執行,這就是單線程的特性。 而 js 的任務分為兩種,同步任務和異步任務:

  • 同步任務就是按照順序一個一個的執行任務,后一個任務要執行必須等它前一個任務完成
  • 異步任務(比如回調)不會占用主線程,會被塞到一個任務隊列,等主線程的任務執行完畢,就會把這個異步任務隊列里的任務放回主線程依次執行

用一個丑但易懂的圖來表示:

所以結果輸出是這樣就很好理解了:

 

Event Loop(事件循環)

被稱作事件循環的原因在於,同步的任務可能會生成新的任務,因此它一直在不停的查找新的事件並執行。一次循環的執行稱之為 tick,在這個循環里執行的代碼被稱作 task,而整個過程是不斷重復的。

console.log(1);

setTimeout(()=>{
  console.log(2);
},1000);

while (true){}

上面代碼在輸出 1 之后(謹慎使用!我的瀏覽器就被卡死了~),定時器被塞到任務隊列里,然后主線程繼續往下執行,碰到一個死循環,導致任務隊列里的任務永遠不會被執行,因此不會輸出 2

事件隊列

除了我們的主線程之外,任務隊列分為 microtaskmacrotask,通常我們會稱之為微任務和宏任務。 microtask 這一名詞在js中是個比較新的概念,我們通常是在學習 ES6 的 Promise 時才初次接觸到。

  • 執行優先級上,主線程任務 > microtask > macrotask。
  • 典型的 macrotask 有 setTimeout 和 setInterval,以及只有 IE 支持的 setImmediate,還有 MessageChannel等,ES6的 Promise 則是屬於 microtask
console.log(1)

setTimeout(function(){
    console.log(2)
})

Promise.resolve().then(function(){
    console.log('promise1')
}).then(function(){
    console.log('promise2')
})

console.log(4)

根據執行順序,上面代碼的輸出結果很容易就能得出了:

nextTick

讓我們回到上面的主題,Vue 的 nextTick方法,

源碼 不難發現,Vue 在內部嘗試對異步隊列使用原生的setImmediateMessageChannel和 Promise.then

如果當前執行環境不支持,就采用setTimeout(fn, 0)代替。

 

什么時候需要用Vue.nextTick():

  • 你在Vue生命周期的created()鈎子函數進行的DOM操作一定要放在Vue.nextTick()的回調函數中。原因是什么呢,原因是在created()鈎子函數執行的時候DOM 其實並未進行任何渲染,而此時進行DOM操作無異於徒勞,所以此處一定要將DOM操作的js代碼放進Vue.nextTick()的回調函數中。與之對應的就是mounted鈎子函數,因為該鈎子函數執行時所有的DOM掛載和渲染都已完成,此時在該鈎子函數中進行任何DOM操作都不會有問題 。
  • 在數據變化后要執行的某個操作,當你設置 vm.someData = 'new value'DOM並不會馬上更新,而是在異步隊列被清除,也就是下一個事件循環開始時執行更新時才會進行必要的DOM更新。如果此時你想要根據更新的 DOM 狀態去做某些事情,就會出現問題。。為了在數據變化之后等待 Vue 完成更新 DOM ,可以在數據變化之后立即使用 Vue.nextTick(callback) 。這樣回調函數在 DOM 更新完成后就會調用。
  • mounted 不會承諾所有的子組件也都一起被掛載。如果你希望等到整個視圖都渲染完畢,可以用 vm.$nextTick 替換掉 mounted
mounted: function () {
this.$nextTick(function () {

// Code that will run only after the
// entire view has been rendered
})
}


免責聲明!

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



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