Suspense
什么是 Suspense?
Suspense 是 Vue3 中的一个内置组件,用于处理异步组件的加载状态。它允许我们在等待异步组件加载完成时显示一个 fallback 内容,从而提供更好的用户体验。
1. 基本用法
Suspense 组件有两个插槽:
default:用于放置异步组件fallback:用于放置加载状态的 fallback 内容
示例:基本 Suspense
<template>
<div class="app">
<h1>Suspense 示例</h1>
<button @click="showAsyncComponent = !showAsyncComponent">
{{ showAsyncComponent ? '隐藏' : '显示' }} 异步组件
</button>
<Suspense v-if="showAsyncComponent">
<!-- 默认插槽:异步组件 -->
<AsyncComponent />
<!-- fallback 插槽:加载状态 -->
<template #fallback>
<div class="loading">
<div class="spinner"></div>
<p>加载中...</p>
</div>
</template>
</Suspense>
</div>
</template>
<script setup>
import { ref } from 'vue';
// 异步组件
const AsyncComponent = () => import('./AsyncComponent.vue');
const showAsyncComponent = ref(false);
</script>
<style scoped>
.app {
padding: 20px;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #42b883;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
AsyncComponent.vue 组件
<template>
<div class="async-component">
<h2>异步组件</h2>
<p>这是一个异步加载的组件</p>
<p>数据:{{ data }}</p>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const data = ref('');
// 模拟异步数据加载
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('异步加载的数据');
}, 2000);
});
}
// 使用 await 等待数据加载
const result = await fetchData();
data.value = result;
</script>
<style scoped>
.async-component {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #f9f9f9;
}
</style>
2. 异步组件
Suspense 通常与异步组件一起使用。在 Vue3 中,我们可以使用 defineAsyncComponent 函数创建异步组件,或者直接使用动态导入。
示例:使用 defineAsyncComponent
<template>
<Suspense>
<AsyncComponentWithOptions />
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue';
// 使用 defineAsyncComponent 创建异步组件
const AsyncComponentWithOptions = defineAsyncComponent({
// 加载函数
loader: () => import('./AsyncComponent.vue'),
// 加载状态的 fallback 组件
loadingComponent: LoadingComponent,
// 加载超时时间
timeout: 3000,
// 加载失败时显示的组件
errorComponent: ErrorComponent,
// 错误信息传递给 errorComponent 的 props
errorComponentProps: {
message: '加载失败,请重试'
}
});
// 加载组件
const LoadingComponent = {
template: '<div class="loading">加载中...</div>'
};
// 错误组件
const ErrorComponent = {
props: ['message'],
template: '<div class="error">{{ message }}</div>'
};
</script>
3. 多个异步组件
Suspense 可以包含多个异步组件,它会等待所有异步组件加载完成后再显示内容。
示例:多个异步组件
<template>
<Suspense>
<div class="async-components">
<AsyncComponent1 />
<AsyncComponent2 />
<AsyncComponent3 />
</div>
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue';
const AsyncComponent1 = defineAsyncComponent(() => import('./AsyncComponent1.vue'));
const AsyncComponent2 = defineAsyncComponent(() => import('./AsyncComponent2.vue'));
const AsyncComponent3 = defineAsyncComponent(() => import('./AsyncComponent3.vue'));
</script>
4. 错误处理
当异步组件加载失败时,我们可以使用 onErrorCaptured 钩子来处理错误,或者使用 errorCaptured 选项。
示例:错误处理
<template>
<div>
<Suspense>
<AsyncComponentWithError />
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
<div v-if="error" class="error">
<p>加载失败:{{ error.message }}</p>
<button @click="reload">重试</button>
</div>
</div>
</template>
<script setup>
import { ref, onErrorCaptured } from 'vue';
const error = ref(null);
let AsyncComponentWithError;
// 初始加载
loadComponent();
function loadComponent() {
error.value = null;
AsyncComponentWithError = () => import('./AsyncComponentWithError.vue');
}
function reload() {
loadComponent();
}
// 捕获错误
onErrorCaptured((err) => {
error.value = err;
return true; // 阻止错误继续传播
});
</script>
AsyncComponentWithError.vue
<template>
<div>这是一个会出错的异步组件</div>
</template>
<script setup>
// 模拟加载错误
throw new Error('组件加载失败');
</script>
5. Suspense 的使用场景
- 异步组件加载
- 异步数据获取
- 动态组件切换
- 路由懒加载
- 大型组件的按需加载
6. Suspense 的最佳实践
- 为 Suspense 提供有意义的 fallback 内容,如加载指示器或骨架屏
- 设置合理的加载超时时间,避免用户等待过长时间
- 提供错误处理机制,当加载失败时显示友好的错误信息
- 考虑使用骨架屏(Skeleton Screen)提升用户体验
- 避免在 Suspense 中嵌套过多的异步组件,可能会导致加载时间过长
- 结合路由懒加载使用,提高应用的初始加载性能
7. Suspense 与其他 API 的结合
与 Teleport 结合
Suspense 可以与 Teleport 结合使用,实现异步组件的跨 DOM 树渲染。
示例:Suspense 与 Teleport
<template>
<Teleport to="body">
<Suspense>
<AsyncModal />
<template #fallback>
<div class="modal-overlay">
<div class="loading">加载中...</div>
</div>
</template>
</Suspense>
</Teleport>
</template>
与 KeepAlive 结合
Suspense 可以与 KeepAlive 结合使用,实现异步组件的缓存。
示例:Suspense 与 KeepAlive
<template>
<KeepAlive>
<Suspense>
<AsyncComponent />
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
</KeepAlive>
</template>
8. Suspense 的限制
- Suspense 只能用于异步组件或包含异步 setup() 函数的组件
- Suspense 不支持服务器端渲染(SSR)
- Suspense 的错误处理需要使用 onErrorCaptured 钩子
- Suspense 不能嵌套过深,否则会影响性能