
来几个小需求
在给变量赋值后获取元素的内容
<script setup>
import { onMounted, ref } from "vue";
let msg=ref("beforeChanged")
onMounted(()=>{
msg.value='changed'
console.log(document.getElementById("msg").innerHTML)
})
</script>
<template>
<div>
<div id="msg">{{ msg }}</div>
</div>
</template>
控制台打印的是beforeChanged,而我们想要的结果是changed.
再如果我们想要在给变量赋值后立即调用子组件的方法获取变化后的数据,同样数据为旧值
<script setup>
import 子组件
import { onMounted, ref } from "vue";
let msg=ref("beforeChanged")
let msgRef=ref(null)
onMounted(()=>{
msg.value='changed'
msgRef.value.getMsg() // 控制台打印beforeChanged
})
</script>
<template>
<div>
<TestNextTick ref="msgRef" :msg="msg" />
</div>
</template>
// 子组件 TestNextTick
<script setup>
const props=defineProps({
msg: String
})
function getMsg(){
console.log(props.msg)
}
defineExpose({
getMsg
})
</script>
异步更新机制
Vue在更新DOM时是异步执行的。只要监听到数据变化时,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入队列中一次。然后,在下一个事件循环中,Vue刷新队列并执行实际工作。Vue2.x在内部对异步队列尝试使用原生的Promise.then、MutationObserver和setImmediate,如果执行环境不支持,则会采用setTimeout(fn, 0)代替。Vue3不再支持IE11,所以nextTick直接使用Promise
为了在数据更新操作之后操作DOM,Vue提供了nextTick函数方便我们使用
使用nextTick
我们看看源码的格式
const resolvedPromise: Promise<any> = Promise.resolve()
let currentFlushPromise: Promise<void> | null = null
export function nextTick(
this: ComponentPublicInstance | void,
fn?: () => void
): Promise<void> {
const p = currentFlushPromise || resolvedPromise
return fn ? p.then(this ? fn.bind(this) : fn) : p
}
通过源码我们可以灵活使用nextTick了,可以看到最终目的就是用 Promise.resolve().then 将 fn 转换成一个微任务,加入微任务队列
// 若我们直接调用nextTick,不传任何回调函数,即nextTick(),则fn为undefined,因此直接返回p
// 而currentFlushPromise默认值为null,所以p=resolvedPromise,即p=Promise.resolve()
// 由于p是异步的,所以我们可以这样去使用
const sampleFun=async ()=>{
await nextTick()
console.log(document.getElementById("msg").innerHTML);
msgRef.value.getMsg();
}
// 也可以这样去使用
nextTick().then(() => {
console.log(document.getElementById("msg").innerHTML);
msgRef.value.getMsg();
});
// 通过异步更新机制,nextTick()后面的代码会在nextTick()执行完后,再次获取执行权时才被执行
// 此时数据已经更新
// 若在调用nextTick函数时传入一个回调函数fn,则fn会在Promise.resolve().then()中执行
nextTick(() => {
console.log(document.getElementById("msg").innerHTML) // changed
msgRef.value.getMsg();// changed
});
现在会使用了,喝口水,我们继续看看是怎么实现的
总结
总的来说 nextTick 的实现主要利用了
- 利用
Promise.resolve().then()将任务推入Micro Task Queue,借助引擎的Event Loop机制处理队列中的任务 - 处理异步任务与回调,对于新添加的异步任务也递归的处理完成。这与引擎处理
Task Queue的逻辑是一致的