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()
}
})()