前言
nextTick()
官方文档的定义:等待下一次 DOM 更新刷新的工具方法
nextTick()
可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
但是在
nextTick()
回调函数或者 await 返回的 Promise 后获取DOM能确保是最新的吗?下面请看例子:
例子1
通过defineAsyncComponent引入子组件
<Child />
子组件:
<template>
<div>
Child
</div>
</template>
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log('mounted Child')
})
</script>
父组件:
<template>
<button @click="showChild">显示子组件</button>
<div id="container" v-if="show">
<Child />
</div>
</template>
<script setup>
import { defineAsyncComponent, ref, nextTick } from "vue";
const Child = defineAsyncComponent(() => import("./Child.vue"));
const show = ref(false);
function nextTickCallback() {
console.log('nextTickCallback')
console.log("container内容:" + document.getElementById("container").textContent);
}
function showChild() {
show.value = true;
nextTick(nextTickCallback);
}
</script>
效果:
根据运行效果,我们发现nextTickCallback
在 mounted Child
之前,并且没有正确获取到DOM的状态。那么是因为container的内容是Vue组件
的原因吗?为了解答疑问,我们再看一个例子:
例子2
直接import子组件
<template>
<button @click="showChild">显示子组件</button>
<div id="container" v-if="show">
<Child />
</div>
</template>
<script setup>
import { ref, nextTick } from "vue";
import Child from './Child.vue'
const show = ref(false);
function nextTickCallback() {
console.log('nextTickCallback')
console.log("container内容:" + document.getElementById("container").textContent);
}
function showChild() {
show.value = true;
nextTick(nextTickCallback);
}
</script>
上面例子把defineAsyncComponent
惰性加载组件改为直接import
,我们发现nextTickCallback
在 mounted Child
之后,并且能够正确获取DOM状态了,所以把defineAsyncComponent
改为直接导入,就一定能获取到正确的DOM状态吗?我们再再看一个例子:
例子3
子组件async setup()
<Child />
子组件:
<template>
<div>
Child
</div>
</template>
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log('mounted Child')
})
await Promise.resolve()
</script>
父组件:
<template>
<button @click="showChild">显示子组件</button>
<div id="container" v-if="show">
<Suspense>
<Child />
</Suspense>
</div>
</template>
<script setup>
import { ref, nextTick } from "vue";
import Child from './Child.vue'
const show = ref(false);
function nextTickCallback() {
console.log('nextTickCallback')
console.log("container内容:" + document.getElementById("container").textContent);
}
function showChild() {
show.value = true;
nextTick(nextTickCallback);
}
</script>
上面例子把子组件改为async setup()后,我们发现nextTickCallback
在 mounted Child
之前,也没有正确获取到DOM的状态。
如何保证渲染完成再执行相应操作?
前面的例子举证的是修改状态后,立即获取DOM内容,但是这种场景在实际开发的过程中比较少见。以下才是更加常见的例子:
<Child />
子组件:
<template>
<div>
Child
</div>
</template>
<script setup>
import { onMounted, defineExpose } from 'vue'
onMounted(() => {
console.log('mounted Child')
})
const say = () => {
console.log('hi')
}
defineExpose({
say
})
</script>
父组件:
<template>
<button @click="showChild">显示子组件</button>
<div v-if="show">
<Child ref="childRef" />
</div>
</template>
<script setup>
import { defineAsyncComponent, ref, nextTick } from "vue";
const Child = defineAsyncComponent(() => import("./Child.vue"));
const show = ref(false);
const childRef = ref();
function showChild() {
show.value = true;
nextTick(() => {
// 报错:TypeError: Cannot read properties of undefined (reading 'say')
childRef.value.say();
});
}
</script>
以上例子,需要渲染子组件后,立即调用子组件内部的say
方法,但是在nextTick
回调里面<Child />
还没有mounted
,所以无法通过ref
访问。
要想在子组件渲染完成后,立即执行子组件内部的方法,我们可以通过监听VNode 生命周期事件实现:
<template>
<button @click="showChild">显示子组件</button>
<div v-if="show">
<Child ref="childRef" @vue:mounted="mountedChild" />
</div>
</template>
<script setup>
import { defineAsyncComponent, ref, nextTick } from "vue";
const Child = defineAsyncComponent(() => import("./Child.vue"));
const show = ref(false);
const childRef = ref();
let mountedChildCallback = new Set()
const mountedChild = () => {
mountedChildCallback.forEach((cb) => cb());
mountedChildCallback = null;
};
function showChild() {
show.value = true;
// 通过判断mountedChildCallback是否为Truthy判断子组件是否mounted
if (mountedChildCallback) {
mountedChildCallback.add(() => {
childRef.value.say();
})
} else {
childRef.value.say();
}
}
</script>
总结
- DOM内容为async setup()组件和异步组件,
nextTick
的回调函数不能获取到“正确”的DOM内容。 - async setup()组件和异步组件可通过监听VNode 生命周期事件实现子组件渲染完后立即调用内部方法。
结语
感谢您耐心阅读这篇文章。如果您觉得内容对您有帮助或启发,请不吝点赞支持。如果您发现文章中的任何错误或需要改进的地方,欢迎您指正批评