Vue3 nextTick 本质是优先在本轮事件循环的微任务阶段异步执行逻辑。如果浏览器不支持微任务,则在下一轮事件循环后异步执行。
一. 特性一览
二. 伪代码实现
if(当前环境支持 Promise){
Promise.then 派发 timerFunc
} else if (当前环境支持 MutationObserver) {
MutationObserver 派发 timerFunc
} else if (当前环境支持 setImmediate) {
setImmediate 派发 timerFunc
} else {
setTimeout 派发 timer Func
}
三. 为什么要优先选择微任务?
- 在程序流中更早的得到执行:微任务会在本轮事件循环结束前执行,优于下一轮宏任务的执行。
- 避免竞态条件:宏任务种类很多,随时可能会有定时器,网络请求等任务穿插,nextTick作为微任务可以确定出在宏任务之前执行。
- 与Vue2的设计保持一致。
四. nextTick 真的能拿到最新的DOM么?
网上几乎所有资料都说nextTick能拿到最新DOM的绝对性,
就连Vue官网也将 nextTick 定义为 “等待下一次 DOM 更新刷新的工具方法。”
如此权威的发言,让笔者的反对言论不胜惶恐。
基于笔者对Vue3源码的理解和思考,Vue3 的模板更新逻辑(即,组件的 render Effect),实际上也会作为微任务,被推进异步执行队列。而 nextTick 的回调也作为被微任务推入异步执行队列,和 render Effect 为伍,Vue3并没有对 nextTick 的回调和 render Effect 进行优先级排列,这就意味着:
- nextTick的回调 若比 render Effect 更早的被推入微任务队列,那么 nextTick 的回调将优先于 render Effect 执行,此时将无法在 nextTick 回调中拿到最新的DOM!
为了验证笔者这一想法是否成真,笔者特别的将 官网提供的 nextTick 实例改造,并运行,结果证明我的结论并无错误:
template:
<template>
<button id="counter" @click="increment">{{ count }}</button>
</template>
script:
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
async function increment() {
// 将官网提供的nextTick提到 count.value++ 前执行
nextTick(() => {
/**
* 将 nextTick 回调推入微任务队列的首位
*/
console.log('second', document.getElementById('counter').textContent) // 0
})
/**
* 触发响应式变量更新,该组件的 render effect 将从 count.value 的 depSet 中取出,
* 并推入微任务队列中,此时 nextTick 回调在 render effect 前,render effect 在后。
*/
count.value++
/**
* 下面这行代码是同步获取DOM,微任务队列还未启动执行,render effect未执行,无法得到最新DOM
*/
console.log('first', document.getElementById('counter').textContent) // 0
/**
* 当前主执行栈中任务已空,开始执行微任务队列中的任务。
* nextTick回调执行 -> render effect执行。
*
* 当执行nextTick回调时,render effect同样未执行,DOM未来得及更新,因此nextTick回调
* 中无法获取模板更新后的DOM,console值依旧是0!
*/
}
</script>