对我们的下一本书感兴趣吗?了解更多关于 使用 React 构建大型 JavaScript Web 应用程序

性能模式

异步组件

在开发大型 Web 应用程序时,性能至关重要。页面加载速度和交互元素的响应能力会极大地影响用户体验。随着 Web 应用程序规模和复杂性的不断增长,确保仅在需要时加载大型代码包变得至关重要。Vue 中的异步组件应运而生。

从我们之前的 文章 中,我们已经了解到组件是构建 UI 的基本构建块。通常,当我们使用组件时,它们会自动加载和解析,即使它们并不立即需要。

另一方面,异步组件允许我们以一种仅在需要时或满足特定条件时才加载和解析的方式定义组件。让我们通过一个练习来更好地理解这一点。

假设我们有一个简单的模态组件,当从父级单击按钮时它会呈现。Modal.vue 组件文件将只包含决定模态外观的模板和样式。

<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 组件中,我们可以渲染模态组件和一个按钮,当单击按钮时,使用一个反应式布尔值 (showModal) 切换模态组件的可见性。模态根据 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>

当我们单击“显示模态”按钮时,模态将显示在页面上。

Open and close modal

从这个例子中,我们可以看到模态组件只在特定情况下显示 - 当用户单击“显示模态”按钮时。尽管如此,与该组件相关的 JavaScript 包 **在整个网页加载时自动加载**,即使在模态变得可见之前。这可以从我们浏览器的网络日志中看到。

Modal bundle loaded on initial page load

对于大多数情况来说,这很好。但是,在模态包大小非常大或应用程序具有大量此类组件的情况下,这会导致初始加载时间延迟。随着每个新增的包,即使它与很少使用的组件相关,初始页面加载所需的时间也会增加。

defineAsyncComponent

这就是 Vue 允许我们使用 defineAsyncComponent() 函数将应用程序划分为更小的块,通过异步加载组件来实现这一点。

import { defineAsyncComponent } from "vue";

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...load component from the server
    resolve(/* loaded component */);
  });
});

defineAsyncComponent() 函数接受一个加载器函数,该函数返回一个 Promise,该 Promise 解析为导入的组件。Promise 的 resolve 回调将在组件成功加载时调用,而 reject 回调将在加载过程中出现任何错误时调用。

但是,我们并不需要像上面那样定义异步组件函数,而是可以使用 动态导入 异步加载 ECMAScript 模块(即在我们的例子中是组件)。这是通过 import() 语法实现的。

import { defineAsyncComponent } from "vue";

export const AsyncComp = defineAsyncComponent(() =>
  import("./components/MyComponent.vue")
);

让我们看看它在模态示例中的作用。我们将创建一个名为 AsyncModal.js 的新文件,并在该文件中,我们将从 vue 库中导入 defineAsyncComponent() 函数,并将 AsyncModal 常量分配给 defineAsyncComponent() 函数调用。

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 bundle not initially loaded on initial page load

当我们单击按钮触发模态显示时,我们会注意到该包随后在异步加载时,模态组件正在被渲染。

Modal bundle asynchronously loaded

加载和错误 UI

使用 defineAsyncComponent(),Vue 为开发人员提供的不仅仅是一种异步加载组件的方式。它还提供了在加载过程中向用户显示反馈和处理任何潜在错误的功能。这确保即使在网络条件不理想或异步加载过程中出现任何错误时,也能提供流畅的用户体验。

loadingComponent

有时我们可能希望在获取组件时向用户提供视觉反馈,尤其是在加载时间很长的情况下。为了实现这一点,defineAsyncComponent() 有一个 loadingComponent 选项,它允许我们在加载阶段指定一个要显示的组件。

由于我们将在 defineAsyncComponent() 函数中声明额外的选项,因此我们将使用 loader() 函数选项来异步导入模态组件。

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,
});

随着模态组件被异步加载,用户现在将看到“正在加载...”消息。这在快速互联网连接中可能很难看到,因此我们将在浏览器网络日志中模拟一个“慢速 3G”网络,以观察在模态组件包仍在加载时看到“正在加载...”消息的行为。

loading component

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,
});

为了将这可视化,我们可以模拟浏览器开发者工具中的“脱机”网络模式,并尝试启动模态。我们会注意到,当模态组件无法加载时,将显示 Error 组件模板。

error component

经过所有这些更改,我们的应用程序可以看作如下所示。

AsyncModal.js
1import { defineAsyncComponent } from "vue";
2 import Loading from "./Loading.vue";
3 import Error from "./Error.vue";
4
5 export const AsyncModal = defineAsyncComponent({
6 loader: () => import("./Modal.vue"),
7 loadingComponent: Loading,
8 errorComponent: Error
9 });

defineAsyncComponent() 函数接受其他选项,如 delaytimeoutsuspensibleonError(),它们为开发人员提供了对异步加载行为和用户体验更精细的控制。请务必查看 API 文档,了解有关这些属性的更多详细信息。

defineAsyncComponent() 函数可以帮助将 Vue 应用程序的初始加载分解成可管理的块,通过推迟某些组件的加载,直到它们需要时为止。这有助于改善页面加载时间和整体应用程序性能,尤其是在应用程序具有许多包大小很大的组件时。

有用的资源