在Vue和React中都内置了Suspense
组件,该组件用于处理异步组件加载。当Suspense包裹的实际组件内容尚未加载完成时会先展示后备内容,等待组件内容加载完成后再切换成实际组件内容。这可以显著提升用户体验,适用于大数据加载、组件懒加载等场景。
Vue—Suspense
Vue中的<Suspense>
组件有两个插槽:#default 和 #fallback。两个插槽都只允许一个直接子节点。在渲染时都将优先渲染默认插槽中的节点,如果遇到异步组件(使用defineAsyncComponent
定义的组件)或者加载的组件带有异步setup钩子(如下代码)时(官方文档称作异步依赖),将显示后备插槽fallback中的节点(一般是用于加载过程中的占位内容,如加载动画或提示文字)。
export default {
async setup() {}
}
// 或者
<script setup>
const res = await getUser(); // setup顶层有await
</script>
加载状态
在初始渲染时,<Suspense> 将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入挂起状态。在挂起状态期间,展示的是后备内容。当所有遇到的异步依赖都完成后,<Suspense> 会进入完成状态,并将展示出默认插槽的内容。如果在初次渲染时没有遇到异步依赖,<Suspense> 会直接进入完成状态。
进入完成状态后,只有当默认插槽的根节点被替换时,<Suspense> 才会回到挂起状态。组件树中新的更深层次的异步依赖不会造成 <Suspense> 回退到挂起状态。
发生回退时,后备内容不会立即展示出来。相反,<Suspense> 在等待新内容和异步依赖完成时,会展示之前 #default 插槽的内容。这个行为可以通过一个 timeout prop 进行配置:在等待渲染新内容耗时超过 timeout 之后,<Suspense> 将会切换为展示后备内容。若 timeout 值为 0 将导致在替换默认内容时立即显示后备内容。
事件
事件名 | 事件描述 |
---|---|
pending | 当 <Suspense> 进入挂起状态时触发(异步操作开始),适合用来启动加载动画。 |
resolve | 当 <Suspense> 内部所有异步操作完成并切换到正常内容时触发,适合用来停止加载动画或执行完成逻辑。 |
fallback | 当 <Suspense> 渲染备用内容(#fallback 插槽)时触发,适合用来记录进入备用状态的日志或执行额外逻辑。 |
事件的执行顺序:pending—>fallback—>resolve。
<template>
<div>
<!-- 监听这三个事件记录当前状态 -->
<Suspense @pending="setStatus('Pending')" @resolve="setStatus('Resolved')" @fallback="setStatus('Fallback')">
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading fallback...</div>
</template>
</Suspense>
<p>Current Status: {{ status }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
new Promise((resolve) => {
setTimeout(() => {
resolve({ template: `<div>Async Component Content</div>` });
}, 2000);
})
);
const status = ref('');
const setStatus = (newStatus) => status.value = newStatus;
</script>
注意:如果 #default 中没有异步依赖,这些事件不会被触发。且pending 和 fallback 事件通常紧密关联,前者触发时后者也会触发。
嵌套使用
当一个Suspense组件下有多个异步依赖且它们之间互相独立时,由于都处在同一个Suspense边界下,即使其中一些已经加载完成也不会进行渲染,而是会等待所有异步依赖全部加载完成最终才会渲染,这样依赖总是同时加载,可能会影响用户的体验。因此,可以嵌套使用Suspense,即某部分依赖先加载完成后不必等待其它依赖加载完成即可渲染。
可以发现:Suspense的suspensible属性的默认值是false,且可以设置一个超时时间,当默认内容加载时间超时时即不会再加载默认内容而是渲染后备内容。另外,也可以监听该组件的pending、resolve和fallback事件。
<Suspense>
<template #default>
<div>
<AsyncComponentOuter />
<Suspense suspensible> {/* 设置suspensible后所有异步依赖项处理都会交给父级 <Suspense> (包括发出的事件) */}
<template #default>
<AsyncComponentInter />
</template>
<template #fallback>
内层加载中...
</template>
</Suspense>
</div>
</template>
<template #fallback>
外层加载中...
</template>
</Suspense>
React—Suspense
React中主要语法如下:
<Suspense fallback={<Loading />}> {/* 这是suspense默认渲染的内容在加载完成之前渲染的后备内容 */}
<Albums /> {/* 这是suspense默认渲染的内容 */}
</Suspense>
Suspense中同样支持嵌套使用,这样Suspense中的内容就可以逐步被渲染。通过合理嵌套 Suspense,就可以更早地显示部分页面内容,同时等待其他内容的加载,从而提高用户体验。
<Suspense fallback={<BigSpinner />}> {/* Biography的后备内容 */}
<Biography />
<Suspense fallback={<AlbumsGlimmer />}> {/* Panel的后备内容 */}
<Panel>
<Albums />
</Panel>
</Suspense>
</Suspense>
配合lazy
组件使用:
import { lazy, Suspense } from "react";
const LazyComponent = lazy(() => import("./LazyComponent")); // 懒加载该组件
export default function App() {
return (
<div>
<h1>React Suspense Example</h1>
{/* Suspense 组件包裹 LazyComponent */}
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
React中的Suspense也可以结合useTransition
和useDeferredValue
等hooks一起使用来控制后备内容的显示与隐藏。
Suspense在Vue和React中对比
- Vue中该组件有默认插槽和fallback插槽,这两个插槽的内容都不支持多根节点;而React中的默认插槽可以支持多个根节点。
- Vue中Suspense组件可以处理任意的异步逻辑(不仅限于组件,如数据请求),而React默认只支持组件级的懒加载,需要和其他库(如 React Query 或 Relay)集成来支持更复杂的数据加载。
- 另外,在SSR结合Suspense使用上可能存在一定的差异。