Vue.nextTick使用和实现

183 阅读1分钟

1. 使用

引用我认为最官方的官方说法,以下每一个字都认真看👇,不然我捶你👊

Vue.nextTick(callback?: () => void): Promise<void>

当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。 nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。

使用示例

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>
<script>
export default {
  data: () => ({ count: 0 })
  methods: {
    async increment() {
      this.count++
      console.log(document.getElementById('counter').textContent) // DOM还未更新 0
      await nextTick(()=>console.log('DOM此时已挂载,在数据更新前作一些操作'))
      console.log(document.getElementById('counter').textContent) // DOM已更新 1
    }
  }
}
</script>

2. 实现

“ Talk is cheap, show me the code ”

Vue.nextTick = (() => {
  function createTick() {  // 在页面重排重绘之后执行
    // Promise.resolve.then -> MutationObserver -> setImmediate -> setTimeout 
    // 这些都是可以异步回调,异步队列里的任务是在同步任务执行完毕->浏览器重排重绘 之后才执行
    if (Promise) return () => Promise.resolve().then(runTaskQueue) // 满足绝大多数浏览器
    
    // 以下是兼容处理
    if (MutationObserver) { // 在监听的DOM改变后会执行回调
      let count = 1
      let observer = new MutationObserver(runTaskQueue)
      let dom = document.createElement('div') // 创建监听的文本节点
      dom.innerText = count
      observer.observe(dom, { characterData: true })
      return () => (dom.innerText = ++count)
    }
    
    if (setImmediate) return () => setImmediate(runTaskQueue)
    if (setTimeout) return () => setTimeout(runTaskQueue)
  }
  let tick = createTick()

  let taskQueue = []
  let pending = false
  function runTaskQueue() { // 从回调任务队列里弹出回调函数,逐个执行
    while(taskQueue.length) taskQueue.shift()()
    pending = false // 跑完所有回调任务后开启阀门
  }
  
  // 主程序
  return (cb) => {
    // 原理:在注册的异步回调函数被执行之前,往回调队列callbacks里push回调函数
    taskQueue.push(cb)
    if (pending) return
    pending = true // 执行一次tick立刻关闭阀门
    tick()
  }
})()