Vue之nextTick 的原理和用途(一)

官方文檔解釋如下:在下次 DOM 更新循環(huán)結束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個方法,獲取更新后的 DOM。

一、具體來說,異步執(zhí)行的運行機制如下。

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

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


上圖事件循環(huán)說明
簡單來說,Vue 在修改數(shù)據(jù)后,視圖不會立刻更新,而是等同一事件循環(huán)中的所有數(shù)據(jù)變化完成之后,再統(tǒng)一進行視圖更新。

//改變數(shù)據(jù)
vm.message = 'changed'

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

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

圖解:


事件循環(huán)解析

(1)、第一個 tick(圖例中第一個步驟,即'本次更新循環(huán)')

  • 首先修改數(shù)據(jù),這是同步任務。同一事件循環(huán)的所有的同步任務都在主線程上執(zhí)行,形成一個執(zhí)行棧,此時還未涉及 DOM 。
  • Vue 開啟一個異步隊列,并緩沖在此事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。如果同一個 watcher 被多次觸發(fā),只會被推入到隊列中一次。

(2)、第二個 tick(圖例中第二個步驟,即'下次更新循環(huán)')

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

(3)、第三個 tick(圖例中第三個步驟)

  • 此時就是文檔所說的

簡單總結事件循環(huán)
同步代碼執(zhí)行 -> 查找異步隊列,推入執(zhí)行棧,執(zhí)行Vue.nextTick[事件循環(huán)1] ->查找異步隊列,推入執(zhí)行棧,執(zhí)行Vue.nextTick[事件循環(huán)2]...
總之,異步是單獨的一個tick,不會和同步在一個 tick 里發(fā)生,也是 DOM 不會馬上改變的原因。

二、什么時候需要用的Vue.nextTick()

  • (1).你在Vue生命周期的created()鉤子函數(shù)進行的DOM操作一定要放在Vue.nextTick()的回調(diào)函數(shù)中。原因是什么呢,原因是在created()鉤子函數(shù)執(zhí)行的時候DOM 其實并未進行任何渲染,而此時進行DOM操作無異于徒勞,所以此處一定要將DOM操作的js代碼放進Vue.nextTick()的回調(diào)函數(shù)中。與之對應的就是mounted鉤子函數(shù),因為該鉤子函數(shù)執(zhí)行時所有的DOM掛載和渲染都已完成,此時在該鉤子函數(shù)中進行任何DOM操作都不會有問題 。
  • (2).在數(shù)據(jù)變化后要執(zhí)行的某個操作,而這個操作需要使用隨數(shù)據(jù)改變而改變的DOM結構的時候,這個操作都應該放進Vue.nextTick()的回調(diào)函數(shù)中。

四、實例如下

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

new Vue({
  el: '#app',
  data:{  
        showMe:false,
        message:''
        }, 
    methods:{
      getMyWidth() {
        this.showMe = true;
        //this.message = this.$refs.myWidth.offsetWidth;
        //報錯 TypeError: this.$refs.myWidth is undefined
        this.$nextTick(()=>{
            //dom元素更新后執(zhí)行,此時能拿到p元素的屬性
            this.message = this.$refs.myWidth.offsetWidth;
      })
    }
    }
})
//點擊按鈕顯示原本以 v-show = false 隱藏起來的輸入框,并獲取焦點。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>獲取焦點</title>
<style type="text/css">
    .closesou{font-size:30px;color:red;cursor: pointer;}
</style>
<script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div id="app">
    <div class="soubox">
      <button class="showsearch" @click="showsou">搜索</button>
      <div v-if="showit">
        <input type="text" name="" id="keywords">
        <div class="closesou" @click="hidesou">&times;</div>
      </div>
    </div>
</div>
<script>
new Vue({
  el: '#app',
  // created:function(){
  //    this.$nextTick(function () {
  //         // DOM 更新了
  //         document.getElementById("keywords").focus()
  //       })
  // },
  data:{  
        showit:true
        }, 
    methods:{
      showsou(){
        this.showit = true
        this.$nextTick(function () {
          // DOM 更新了
          document.getElementById("keywords").focus()
        })
      },
      hidesou(){
        this.showit = false
      }
    }
})
</script>
</body>
</html>
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容