首頁 > 軟體

Vue NextTick介紹與使用原理

2022-08-05 14:00:17

一、NextTick是什麼

定義

在下次 DOM 更新迴圈結束之後執行延遲迴撥。在修改資料之後立即使用這個方法,獲取更新後的 DOM。

理解

Vue在更新DOM時時非同步執行的。當資料發生變化時,Vue將開啟一個非同步更新佇列,檢視需要等佇列中所有資料變化完成之後,再統一進行更新。

舉例:

<body>
    <div id="app">
        <div>
            <button id="firstBtn" @click="testClick()" ref="aa">{{testMsg}}</button>
          </div>
    </div>
</body>
</html>
<script>
    new Vue({
        el:'#app',
        data () {
    return {
      testMsg:"原始值",
    }
  },
  methods:{
    testClick:function(){
      this.testMsg="修改後的值";
      console.log(this.$refs.aa.innerText);   //that.$refs.aa獲取指定DOM,輸出:原始值
      console.log(document.querySelector('#firstBtn').innerText)
    }
  }
})
</script>

這時候想獲取頁面最新的DOM節點,卻發現獲取到的仍然是舊值。

這是因為message資料在發現變化的時候,vue並不會立刻去更新Dom,而是將修改資料的操作放在了一個非同步操作佇列中,如果我們一直修改相同資料,非同步操作佇列還會進行去重,等待同一事件迴圈中的所有資料變化完成之後,會將佇列中的事件拿來進行處理,進行DOM的更新。

為什麼要有nexttick

{{num}}
for(let i=0; i<100000; i++){
    num = i
}

如果沒有 nextTick 更新機制,那麼 num 每次更新值都會觸發檢視更新(上面這段程式碼也就是會更新10萬次檢視),有了nextTick機制,只需要更新一次,所以nextTick本質是一種優化策略。

二、使用場景

this.testMsg="修改後的值";
console.log(this.$refs.aa.innerText);   //that.$refs.aa獲取指定DOM,輸出:原始值
Vue.nextTick(function(){
  console.log(document.querySelector('#firstBtn').innerText)
})

元件內使用vm.$nextTick() 實體方法只需要通過this. $nextTick(),並且回撥函數中的 this 將自動繫結到當前的 Vue 範例上

this.message = '修改後的值'
console.log(this.$refs.aa.innerText) // => '原始的值'
this.$nextTick(function () {
    console.log(this.$refs.aa.innerText) // => '修改後的值'
})

$nextTick() 會返回一個 Promise 物件,可以是用async/await完成相同作用的事情

await Vue.nextTick()
console.log(document.querySelector('#firstBtn').innerText)

三、實現原理

  • callbacks也就是非同步操作佇列
  • callbacks新增回撥函數後又執行了timerFunc函數,pending是用來標識同一個時間只能執行一次。
export function nextTick(cb?: Function, ctx?: Object) {
  let _resolve;
  // cb 回撥函數會經統一處理壓入 callbacks 陣列
  callbacks.push(() => {
    if (cb) {
      // 給 cb 回撥函數執行加上了 try-catch 錯誤處理
      try {
        cb.call(ctx);
      } catch (e) {
        handleError(e, ctx, 'nextTick');
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });
  // 執行非同步延遲函數 timerFunc
  if (!pending) {
    pending = true;
    timerFunc();
  }
  // 當 nextTick 沒有傳入函數引數的時候,返回一個 Promise 化的呼叫
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve;
    });
  }
}
  • timerFunc函數定義,這裡是根據當前環境支援什麼方法則確定呼叫哪個,分別有:Promise.then、MutationObserver、setImmediate、setTimeout;
  • 通過上面任意一種方法,進行降級操作
export let isUsingMicroTask = false
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  //判斷1:是否原生支援Promise
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  //判斷2:是否原生支援MutationObserver
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  //判斷3:是否原生支援setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  //判斷4:上面都不行,直接用setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
  • 無論是微任務還是宏任務,都會放到flushCallbacks使用
  • 這裡將callbacks裡面的函數複製一份,同時callbacks置空
  • 依次執行callbacks裡面的函數
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

小結:

  • 把回撥函數放入callbacks等待執行;
  • 將執行函數放到微任務或者宏任務中;
  • 事件迴圈到了微任務或者宏任務,執行函數依次執行callbacks中的回撥。

到此這篇關於Vue NextTick介紹與使用原理的文章就介紹到這了,更多相關Vue NextTick內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com