异步组件
当开发一个大型的 web 应用时,性能表现是至关重要的。页面的加载速度和页面上可交互元素的响应速度会极大地影响用户体验。随着 web 应用程序的规模和复杂性日益增长,确保代码的按需加载变得非常重要。这时我们在 Vue 中引入了异步组件。
在我们 之前的文章 中,我们知道了组件是构建 UI 的主要区块。通常来说,当我们使用组件时,即便我们需要立即使用它们,它们也会自动加载并解析了。
另一方面,异步组件会在我们需要时,或是满足某些条件时,才对其进行加载和解析。接下来我们通过一个例子来帮助理解。
假设我们有一个简单的 modal 组件,当父元素中的按钮被点击时,才对其进行渲染。Modal.vue
组件只包含模板和样式,定义了 modal 组件的表现形式。
<template>
<div class="modal-mask">
<div class="modal-container">
<div class="modal-body">
<h3>This is the modal!</h3>
</div>
<div class="modal-footer">
<button class="modal-default-button" @click="$emit('close')">OK</button>
</div>
</div>
</div>
</template>
<style>
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
transition: opacity 0.3s ease;
}
.modal-container {
width: 300px;
margin: auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
transition: all 0.3s ease;
}
.modal-body h3 {
margin-top: 0;
color: #42b983;
}
.modal-default-button {
float: right;
}
</style>
在父组件 App
中,我们渲染 modal 和一个按钮。当按钮被点击时,通过一个布尔类型的值(showModal
)来切换 modal 的显示。modal 的显示与否基于响应式数据 showModal
。
<template>
<button id="show-modal" @click="showModal = true">Show Modal</button>
<Modal v-if="showModal" :show="showModal" @close="showModal = false" />
</template>
<script setup>
import { ref } from "vue";
import Modal from "./components/Modal.vue";
const showModal = ref(false);
</script>
当我们点击 ShowModal
按钮,modal 就在页面上显示出来了。
在这个例子中,我们可以看到 modal 组件只会在一中特殊的情况下显示 —— 当用户点击了 Show Modal
按钮后。尽管如此,当页面加载时,与 modal 组件关联的整个 JavaScript 部分都会自动加载,甚至这时 modal 还没有显示。这在浏览器的网络日志中可以看到。
这对于大多数情况都没什么问题。然而,在 modal 组件特别大,或者是整个程序拥有大量此类组件的情况下,就可能会在最初加载页面时出现很长时间的延迟。
定义异步组件 defineAsyncComponent
Vue 允许我们使用 defineAsyncComponent()
定义异步组件,使用异步的方式加载组件,从而将应用程序分成更小的块。
import { defineAsyncComponent } from "vue";
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...从服务端加载组件
resolve(/* 加载组件 */);
});
});
defineAsyncComponent()
函数接收一个加载器函数,该加载器返回一个导入组件的 Promise
。当组件导入成功时,调用 Promise 的 resolve 回调;当组件加载过程中出现问题,则会调用 reject 回调。
然而,如果不想像上面说的那样来定义异步组件,我们也可以使用 动态导入 的方式来异步加载一个 ES 模块(即我们例子中的组件)。这个过程通过 import()
语法来实现。
import { defineAsyncComponent } from "vue";
export const AsyncComp = defineAsyncComponent(() =>
import("./components/MyComponent.vue")
);
我们来看一看上面 modal 例子的实际效果。我们创建一个新文件 AsyncModal.js
,在这个文件中,我们从 vue
的库中导入 defineAsyncComponent()
函数,并且将调用结果分配给常量 AsyncModal
。
import { defineAsyncComponent } from "vue";
export const AsyncModal = defineAsyncComponent();
在我们 defineAsyncComponent()
函数的调用中,我们使用了 import()
语法来异步地导入了我们之前创建的 Modal
组件。
import { defineAsyncComponent } from "vue";
export const AsyncModal = defineAsyncComponent(() => import("./Modal.vue"));
在我们的父元素 App
中,我们现在导入并使用异步组件 AsyncModal
来取代 Modal
组件。
<template>
<button id="show-modal" @click="showModal = true">Show Modal</button>
<AsyncModal v-if="showModal" :show="showModal" @close="showModal = false" />
</template>
<script setup>
import { ref } from "vue";
import { AsyncModal } from "./components/AsyncModal";
const showModal = ref(false);
</script>
伴随着微小的变化,我们的 modal 组件现在可以异步加载了!当我们的网页初始化时,可以看到 Modal 组件不再随着页面加载而自动加载了。
当我们点击按钮来触发 modal 显示时,我们注意到对应的代码块随着 modal 的渲染异步加载了。
加载和错误页面
Vue 通过 defineAsyncComponent()
为开发者提供了不止一种异步加载组件的方法。它还为用户提供了在加载过程中显示反馈并进行错误处理的功能。即使网络条件不佳,或者是异步加载过程中发生了错误,也可以确保流畅的用户体验。
loadingComponent
有时候我们希望在获取组件时,特别是组件加载了很长的时间时,向用户提供一些视觉反馈。defineAsyncComponent()
函数有一个 loadingComponent
选项,可以让我们指定一个在加载期间显示的组件。
由于我们需要在 defineAsyncComponent()
函数中使用额外的选项,我们需要使用 loader()
函数选项来异步加载 modal 组件了。
import { defineAsyncComponent } from "vue";
export const AsyncModal = defineAsyncComponent({
loader: () => import("./Modal.vue"),
});
假设我们在 Loading.vue
文件中定义了一个简单的加载组件,像下面这样:
<template>
<p>Loading...</p>
</template>
然后我们将这个组件传入 defineAsyncComponent()
中的 loadingComponent
选项。
import { defineAsyncComponent } from "vue";
import Loading from "./Loading.vue";
export const AsyncModal = defineAsyncComponent({
loader: () => import("./Modal.vue"),
loadingComponent: Loading,
});
当异步加载 modal 组件时,用户将会看到一个 Loading...
消息。这在平时快速的网络连接状态下很难观测到,我们可以在浏览器的 Network 功能中模拟慢速 3G 网络,在这个模式下可以观察到异步组件的加载状态。
errorComponent
在特殊的情况下(例如网络连接状态不佳),有可能异步组件会加载失败。对于这些特殊场景,我们需要提供错误反馈,这对用户体验至关重要。defineAsyncComponent()
函数提供了一个 errorComponent
选项来应对类似的场景,它允许我们指定一个组件,在异步组件加载失败时显示。
假设我们有一个组件 Error.vue
,其模板的定义如下:
<template>
<p>Error!</p>
</template>
要将这个错误组件集成到我们的异步组件中,我们要将其指定为 errorComponent
选项的值。
import { defineAsyncComponent } from "vue";
import Loading from "./Loading.vue";
import Error from "./Error.vue";
export const AsyncModal = defineAsyncComponent({
loader: () => import("./Modal.vue"),
loadingComponent: Loading,
errorComponent: Error,
});
为了能更直观地看到效果,我们要在浏览器的开发者工具的 Networks 中,将网络设置为模拟离线状态。这时我们注意到当 modal 组件加载失败时,Error
组件就显示出来了。
完成了这一系列的修改后,我们的应用程序如下所示。
在演练场中查看代码defineAsyncCompomnent()
函数可以接收更多的参数,类似 delay
、timeout
、suspensible
、onError()
等,这些选项为开发者提供了对异步加载行为和用户体验的更精细的控制。你可以查看 API 文档,来查看更多关于这些属性的信息。
defineAsyncComponent()
函数可以推迟某些组件的加载,直到需要的时候才允许其加载,以此来将 Vue 程序的初始化流程分解为更多可管理的部分。这将有助于改善页面的加载时间和应用程序的整体性能,特别是当应用程序具有大量大尺寸组件的时候。