异步组件(Async Components)也称为懒加载组件(Lazy-loaded Components)。
默认情况下,Webpack 或 Vite 等构建工具会把你所有的组件打包到一个(或几个)JavaScript 文件中。当用户访问你的网站时,他们必须一次性下载所有代码,这可能导致初始加载时间很长。
异步组件允许你将某些组件(通常是“重量级”或不总是需要立即显示的组件)分离成单独的 JavaScript 文件(chunk) 。这些文件只会在实际需要渲染该组件时才从服务器下载。
主要好处:
- 代码分割 (Code-splitting): 减小初始包的体积。
- 提升性能: 加快应用的初始加载和渲染速度。
1.在 Vue 3 中, 使用 defineAsyncComponent 函数来创建异步组件
举个栗子,骨架屏,准备两个组件,card和skeleton
card.vue
<template>
<div class="card">
<div class="user-info">
<img :src="user.avatar" alt="avatar" />
<div class="user-name">{{ user.name }}</div>
</div>
<hr />
<div class="content">
<p>{{ user.content }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
interface User {
id: number;
name: string;
avatar: string;
content: string;
}
const user = ref<User>({
id: 1,
name: "YaeZed",
avatar: "https://avatars.githubusercontent.com/u/52018740?v=5",
content: "Hello, Vue3!",
});
</script>
<style scoped>
.card {
width: 300px;
height: 300px;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.3);
}
.user-info {
display: flex;
flex-direction: row;
text-align: center;
align-items: center;
}
img {
width: 150px;
height: 150px;
border-radius: 100px;
}
.user-name {
margin-left: 30px;
font-size: 24px;
font-weight: bold;
}
.content {
margin-left: 10px;
}
</style>
skeleton.vue
<template>
<div class="card">
<div class="user-info">
<img src="" alt="" />
<div class="user-name"></div>
</div>
<hr />
<div class="content">
<p></p>
</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped>
.card {
width: 300px;
height: 300px;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.3);
}
.user-info {
display: flex;
flex-direction: row;
text-align: center;
align-items: center;
}
img {
background-color: bisque;
width: 150px;
height: 150px;
border-radius: 100px;
}
.user-name {
width: 70px;
height: 20px;
background-color: bisque;
margin-left: 30px;
font-size: 24px;
font-weight: bold;
}
.content {
background-color: beige;
height: 120px;
width: 300px;
}
</style>
结合使用异步组件和 Suspense
在
App.vue中 ,使用Suspense来加载card.vue(作为异步组件),并在加载时显示skeleton.vue
<template>
<div>
<h1>应用</h1>
<p>下面的卡片是异步加载的...</p>
<Suspense>
<template #default>
<AsyncUserCard />
</template>
<template #fallback>
<CardSkeleton />
</template>
</Suspense>
</div>
</template>
<script setup lang="ts">
// 导入 defineAsyncComponent 来创建异步组件
import { defineAsyncComponent } from 'vue';
// 1. 同步导入骨架屏
//
// "fallback" 内容必须是立即(同步)可用的,
// 所以我们像平常一样导入 skeleton.vue。
import CardSkeleton from './skeleton.vue';
// 2. 异步导入你的卡片组件
//
// 我们使用 `defineAsyncComponent` 来“包装”你的 card.vue。
// 这告诉 Vue 这是一个异步组件,应该懒加载。
const AsyncUserCard = defineAsyncComponent(() =>
import('./card.vue')
);
// ----------------------------------------------------
// 💡 (可选) 如何在本地测试骨架屏:
//
// 在本地开发中,`card.vue` 加载得太快,你可能
// 看不到骨架屏。你可以像这样模拟一个 2 秒的网络延迟:
//
// const AsyncUserCard = defineAsyncComponent(() => {
// return new Promise(resolve => {
// setTimeout(() => {
// // @ts-ignore
// resolve(import('./card.vue'));
// }, 2000); // 延迟 2 秒
// });
// });
// ----------------------------------------------------
</script>
<style>
/* 一些全局样式 */
body {
font-family: sans-serif;
padding: 20px;
}
</style>
流程
-
用户加载
App.vue。 -
Suspense开始渲染。它尝试渲染#default插槽。 -
它发现
#default里的AsyncUserCard是一个异步组件,并且尚未加载。 -
Suspense立即切换到渲染#fallback插槽,显示CardSkeleton。 -
同时,Vue 在后台开始下载
card.vue对应的 JavaScript 文件。 -
下载和解析完成后,
Suspense自动用AsyncUserCard的内容替换掉CardSkeleton。
2.另一种触发 Suspense 的方式:async setup
修改一下card.vue
<template>
<div class="card">
<div class="user-info">
<img :src="user.avatar" alt="avatar" />
<div class="user-name">{{ user.name }}</div>
</div>
<hr />
<div class="content">
<p>{{ user.content }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
interface User {
id: number;
name: string;
avatar: string;
content: string;
}
// 1. 模拟一个异步数据获取 (例如 API 调用)
const fetchUserData = (): Promise<User> => {
return new Promise(resolve => {
setTimeout(() => {
console.log("数据获取完成!");
resolve({
id: 1,
name: "YaeZed (来自 API)",
avatar: "https://avatars.githubusercontent.com/u/52018740?v=4",
content: "Hello, from async setup!",
});
}, 2000); // 模拟 2 秒的 API 调用
});
};
// 2. 在 <script setup> 顶层使用 await
//
// Vue 会自动将 `setup` 变为 `async setup`。
// `Suspense` 将会等待这个 `await` (fetchUserData) 完成。
const user = ref<User>(await fetchUserData());
</script>
<style scoped>
/* 样式不变 */
.card {
width: 300px;
height: 300px;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.3);
}
.user-info {
display: flex;
flex-direction: row;
text-align: center;
align-items: center;
}
img {
width: 150px;
height: 150px;
border-radius: 100px;
}
.user-name {
margin-left: 30px;
font-size: 24px;
font-weight: bold;
}
.content {
margin-left: 10px;
}
</style>
现在,card.vue 自身包含了一个异步操作。App.vue 可以这样写:
<template>
<Suspense>
<template #default>
<UserCard />
</template>
<template #fallback>
<CardSkeleton />
</template>
</Suspense>
</template>
<script setup lang="ts">
// 这次可以同步导入 card
import UserCard from './card.vue';
import CardSkeleton from './skeleton.vue';
</script>
在这个版本中,同步导入了 UserCard,但因为 UserCard 内部有 async setup,Suspense 仍然会捕获这个异步状态,并在 await fetchUserData() 完成之前显示 CardSkeleton。
3.处理加载失败
异步组件在网络不稳定或资源不存在时可能会加载失败。为了防止整个页面崩溃,通常会配合 Vue 的 onErrorCaptured 钩子或专门的“错误边界”组件来处理异常。
可以通过 defineAsyncComponent 的高级配置项来处理超时或加载失败的情况:
const AsyncUserCard = defineAsyncComponent({
loader: () => import('./card.vue'),
// 加载异步组件时使用的组件(类似 skeleton)
loadingComponent: CardSkeleton,
// 展示加载组件前的延迟时间。默认:200ms
delay: 200,
// 如果提供了一个加载组件,在超时前它将被展示
timeout: 3000,
// 加载失败时使用的组件
errorComponent: ErrorComponent,
});
或者在 Suspense 的父组件中使用 onErrorCaptured 钩子来捕获异步依赖抛出的任何错误。
参考文章
小满zs 学习Vue3 第十八章(异步组件&代码分包&suspense)xiaoman.blog.csdn.net/article/det…