Vue中如何监听插槽变化并等待异步子组件加载完成?
问题
我有一个EdgeSplit组件,可接收多个插槽子组件。当部分子组件是使用Suspense的异步组件时,组件无法执行对应的转换操作,因为此时数据尚未加载。同步子组件(如示例中的TresMesh)能正常工作,但包裹在Suspense中的异步Headphones组件不行。由于并非DOM元素,MutationObserver无法生效;使用setTimeout虽能解决问题,但并非理想方案。请问如何让EdgeSplit组件等待所有子组件加载完成后再执行applyModifier方法?
相关代码示例
父组件使用代码:
<EdgeSplit :angle="Math.PI * 2"> <!-- 此组件工作正常 --> <TresMesh name="green" :scale="0.5" :position-x="2"> <TresIcosahedronGeometry :args="[1, 3]" /> <TresMeshStandardMaterial :color="0x00FF00" /> </TresMesh> <!-- 此组件因未加载无法工作 --> <Suspense> <Headphones :scale="0.25" :position-y="-2" /> </Suspense> </EdgeSplit>
EdgeSplit组件部分代码:
if (slots.default) { slots.default().forEach((slot) => { if (slot.type.name === 'Suspense') { // 寻找Suspense resolved时的方法 setTimeout(() => { applyModifier(wrapperRef.value) }, 1000) } else { applyModifier(wrapperRef.value) } }) }
解决方案
方法1:监听Suspense的resolve事件
Vue的Suspense组件提供了resolve事件,会在内部异步组件加载完成时触发。我们可以利用这个事件通知EdgeSplit组件执行转换逻辑。
修改父组件的使用方式,给Suspense绑定事件回调:
<EdgeSplit ref="edgeSplitRef" :angle="Math.PI * 2"> <TresMesh name="green" :scale="0.5" :position-x="2"> <TresIcosahedronGeometry :args="[1, 3]" /> <TresMeshStandardMaterial :color="0x00FF00" /> </TresMesh> <Suspense @resolve="() => $refs.edgeSplitRef.runApplyModifier()"> <Headphones :scale="0.25" :position-y="-2" /> </Suspense> </EdgeSplit>
在EdgeSplit组件中暴露方法,并处理同步组件的初始化:
import { onMounted, ref } from 'vue' export default { setup(props, { slots }) { const wrapperRef = ref(null) const applyModifier = (target) => { // 你的转换逻辑实现 } // 处理同步子组件 const handleSyncComponents = () => { slots.default?.forEach(slot => { if (slot.type.name !== 'Suspense') { applyModifier(wrapperRef.value) } }) } // 暴露给父组件的方法,处理异步组件加载完成后的逻辑 const runApplyModifier = () => { applyModifier(wrapperRef.value) } onMounted(() => { handleSyncComponents() }) return { wrapperRef, runApplyModifier } } }
方法2:让异步组件主动通知
让异步的Headphones组件在自身加载完成后,主动触发事件通知EdgeSplit组件执行转换。
在Headphones组件的挂载钩子中添加事件触发:
// Headphones.vue import { onMounted } from 'vue' export default { setup(props, { emit }) { onMounted(() => { // 通知上层组件自己已加载完成 emit('component-loaded') }) } }
父组件中监听该事件,调用EdgeSplit的方法:
<EdgeSplit ref="edgeSplitRef" :angle="Math.PI * 2"> <TresMesh name="green" :scale="0.5" :position-x="2"> <TresIcosahedronGeometry :args="[1, 3]" /> <TresMeshStandardMaterial :color="0x00FF00" /> </TresMesh> <Suspense> <Headphones :scale="0.25" :position-y="-2" @component-loaded="$refs.edgeSplitRef.runApplyModifier()" /> </Suspense> </EdgeSplit>
EdgeSplit组件的runApplyModifier方法同方法1,确保能统一处理转换逻辑。
方法3:监听Suspense内部状态(需谨慎使用)
Vue的Suspense组件内部维护了isResolved状态,我们可以通过组件实例监听这个状态变化。注意:这属于非公开API,可能随Vue版本更新发生变化。
修改EdgeSplit组件的逻辑:
import { onMounted, ref, watch } from 'vue' export default { setup(props, { slots }) { const wrapperRef = ref(null) const applyModifier = (target) => { // 你的转换逻辑实现 } onMounted(() => { slots.default?.forEach(slot => { if (slot.type.name === 'Suspense') { // 监听Suspense的resolved状态 watch(() => slot.component.isResolved, (isResolved) => { if (isResolved) { applyModifier(wrapperRef.value) } }, { immediate: true }) } else { applyModifier(wrapperRef.value) } }) }) return { wrapperRef } } }
内容的提问来源于stack exchange,提问作者jaime Torrealba Cordova
相关产品推荐
相关产品推荐

