Vue3如何再次触发Suspense的pending状态以刷新组件数据?
问题背景
我在组件的async setup中通过import()加载JSON文件,需要多次用<Suspense>作为加载回退。根据Vue官方文档:
处于resolved状态后,只有当#default插槽的根节点被替换时,
才会回到pending状态。
我当前的实现是用:is指令先切换到一个dummyUI组件,再通过1ms的setTimeout切回目标组件,代码如下:
function childComponentHaveToBeRefreshed() { cType.value = markRaw(dummyUI); // 必须加1ms延时,否则替换操作会被忽略 setTimeout(()=>{ cType.value = markRaw(realUI); },1); };
<Suspense> <component :is="cType"></component> <template #fallback> <Loading></Loading> </template> </Suspense>
想请教:触发Suspense进入新pending状态并重新调用setup()的最优方式是什么?怎么正确替换#default插槽的根节点?
最优实现方案
方案1:给目标组件绑定唯一key触发重建
不需要切换dummy组件,直接给<Suspense>默认插槽里的目标组件绑定唯一key,每次需要刷新时更新这个key,Vue会销毁旧组件实例并创建新实例,从而触发Suspense重新进入pending状态,重新执行setup()。
示例代码:
<template> <Suspense> <RealUI :key="refreshKey" /> <template #fallback> <Loading /> </template> </Suspense> </template> <script setup> import { ref } from 'vue'; import RealUI from './RealUI.vue'; const refreshKey = ref(0); function childComponentHaveToBeRefreshed() { // 每次刷新时更新key,比如自增 refreshKey.value++; } </script>
原理:Vue的key是组件的身份标识,当key变化时,Vue会判定为新组件,销毁旧实例并创建新实例。新组件的async setup会重新执行,Suspense检测到根节点(RealUI)被重建,就会回到pending状态显示fallback。
方案2:动态组件配合key(无需dummy和延时)
如果必须使用动态组件(:is),可以直接通过更新key触发组件重建,不需要dummy组件和setTimeout:
<template> <Suspense> <component :is="realUI" :key="refreshKey" /> <template #fallback> <Loading /> </template> </Suspense> </template> <script setup> import { ref, markRaw } from 'vue'; import RealUI from './RealUI.vue'; const refreshKey = ref(0); const realUI = ref(markRaw(RealUI)); function childComponentHaveToBeRefreshed() { refreshKey.value++; } </script>
即使组件引用没有变化,只要key更新,就会触发组件重建,进而让Suspense重新进入pending状态。
原有方案需要延时的原因
Vue的DOM更新是异步批量执行的,直接连续切换cType的值,Vue会合并两次更新操作,最终只渲染realUI,不会触发组件的销毁与重建。setTimeout的作用是把第二次更新放到下一个事件循环,让Vue先渲染dummyUI再切换回realUI,从而触发Suspense的状态变化。但这种方式存在不必要的DOM操作,效率远不如绑定key的方案。
内容的提问来源于stack exchange,提问作者Anthony Bobenrieth

