副作用刷新时机
Vue 的响应性系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个“tick”中多个状态改变导致的不必要的重复调用。
同一个“tick”的意思是,Vue的内部机制会以最科学的计算规则将视图刷新请求合并成一个一个的"tick",每个“tick”刷新一次视图,比如a=1;b=2;
只会触发一次视图刷新。$nextTick的Tick就是指这个。
继续说,比如有个watchEffect监听了2个变量a和b,我的业务写了a=1;b=2;
,你觉得监听器会调用2次?当然不会,Vue会合并成1次去执行,代码如下,console.log只会执行一次:
<template>
<div>
<button
@click="
r++;
s++;
"
>
{
{ r }} - {
{ s }}
</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
let r = ref(2);
let s = ref(10);
watchEffect(() => {
console.log(r.value, s.value);
});
return {
r,
s,
};
},
};
</script>
在核心的具体实现中,组件的
update
函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时,默认情况下,会在所有的组件update
前执行。
所谓组件的update
函数是Vue内置的用来更新DOM的函数,它也是副作用。这时候有一个问题,就是默认下,Vue会先执行组件DOM update,还是先执行监听器?测一下:
<template>
<div>
<button
id="aa"
@click="
r++;
s++;
"
>
{
{ r }} - {
{ s }}
</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
let r = ref(2);
let s = ref(10);
watchEffect(
() => {
console.log(r.value, s.value);
console.log(document.querySelector('#aa') && document.querySelector('#aa').innerText);
}
);
return {
r,
s,
};
},
};
</script>
点击若干次(比如2次)按钮,得到的结果是:
为什么点之前按钮的innerText打印null?因为事实就是默认先执行监听器,然后更新DOM,此时DOM还未生成,当然是null。
当我第1和2次点击完,你会发现,document.querySelector('#aa').innerText
获取到的总是点击之前DOM的内容。这也说明,默认Vue先执行监听器,所以取到了上一次的内容,然后执行组件update。
Vue 2其实也是这种机制,Vue 2使用this.$nextTick()去获取组件更新完成之后的DOM,在watchEffect里就不需要用this.$nextTick()(也没法用),有一个办法能获取组件更新完成之后的DOM,就是使用:
watchEffect(
() => {
/* ... */
},
{
flush: 'post'
}
)
现在设上flush配置项,重新进入组件,再看看:
没设flush: 'post' | 设了flush: 'post' |
---|---|
|
|
所以结论是,如果要操作“更新之后的DOM”,就要配置flush: 'post'。