一、问题分析
由于vue的异步更新机制,我们在同步代码中是无法获取到更新后的dom的信息的
针对这个问题,我们有三种解决方案获取更新后的dom:
1.nextTick()
2.setTimeout()
3.在微任务中获取
因为更新是在同步任务结束后,执行微任务之前,所以上面三种方式可以得到更新后的dom。
二、执行顺序分析
先看代码,下面的代码中,通过点击使得绑定的按钮元素高度增加,在js中通过打印来去观察不同获取该元素信息的方式的区别。
<template>
<div class="page">
<button
@click="clickButton"
ref="buttonRef"
:style="{ height: `${height}px`, width: '60px' }"
></button>
<div class="info">{{ height }}</div>
</div>
</template>
<script setup lang="ts">
import { nextTick, ref } from "vue";
const height = ref(40);
const buttonRef = ref();
const clickButton = async () => {
height.value = height.value + 10;
console.log(buttonRef.value.style.height, "直接打印"); // 40px
setTimeout(() => {
console.log(buttonRef.value.style.height, "setTimeout0打印1"); // 50px
}, 0);
await console.log("dom已更新");
setTimeout(() => {
console.log(buttonRef.value.style.height, "setTimeout0打印2"); // 50px
}, 0);
setTimeout(() => {
console.log(buttonRef.value.style.height, "setTimeout100打印"); // 50px
}, 100);
nextTick(() => {
console.log(buttonRef.value.style.height, "nextTick打印"); // 50px
});
console.log("测试数据");
console.log(buttonRef.value.style.height, "微任务打印"); // 50px
};
</script>
<style>
.page {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>
我们可以观察到,除了在同步代码中获取按钮元素的信息,都得到了更新后的按钮高度。同时我们可以发现,nextTick中的任务是在微任务队列的末尾的,如果把这段代码提前到同步任务中也是同样的效果。
由此,我们可以得出结论,获取更新后dom的速度,微任务中获取>nextTick中获取>宏任务中获取
三、为什么nextTick让任务进入微任务队列末尾而不是开头或者中间
1.避免无限循环。如果 nextTick 的回调被添加到队列的开头,那么在执行回调的过程中再次调用 nextTick 可能会导致无限循环,因为新产生的 nextTick 任务会立即执行,从而可能不断地往队列中添加新的任务。
2.性能优化。vue的异步更新是为了让同一个Tick中的数据变化完了再更新,将任务放入末尾也是同样的目的,可以尽可能减少不必要的dom操作。
3.便于调试。如果这个任务会到处都是那么数据的变化是难以预测的。
四、为什么优先使用nextTick()
1.nextTick的作用就是告诉vue这个dom更新好了,简单,稳定,可靠,语义强。
2.方便调试,避免无限循环,性能优化。
3.不用像settimeout一样把任务拖到下一个宏任务。