阅读时间约 11 分钟。

本文发布于微信公众号码上花甲,每天 9: 00 准时为你推送高质量前端开发相关文章!更有精品资源供你免费获取!欢迎关注。


我们开发的 React 应用随着规模越来越大,如果还是将所有组件打包到一个 bundle 文件中,就可能会导致应用程序的加载时间变慢。常见的做法是,将程序中的组件打包到单独的文件中以减少应用的加载时间,通常我们会通过“按需加载”的方式来实现这一目的。在本文中,你将学会在 React 程序中关于组件动态导入的相关知识。

1. 什么是动态导入组件?

动态导入组件是指在需要时才加载组件,而不是在应用程序加载时将所有组件都打包到一个 bundle 文件中。在 React 中,我们该如何实现呢?

React 为我们提供了一个 lazy() 方法和 Suspense 组件,这是实现动态导入组件的两个主要工具。

使用 React.lazy(),我们可以轻松地实现按需加载组件,它的语法如下:

const MyComponent = React.lazy(() => import('./MyComponent'));

在上面的代码中,React.lazy() 接收一个函数,该函数返回一个 import() 函数调用。import() 函数是 ECMAScript 动态导入语法的一部分,它允许我们在运行时异步加载一个模块。通过将 React.lazy()import() 函数结合使用,我们便可以按需加载组件。

当我们使用 React.lazy() 时,React 会自动将返回的组件包装在一个 lazy 组件中。因此,我们需要使用 Suspense 组件来渲染这个 lazy 组件。Suspense 组件允许我们在组件加载完成之前显示一个 loading 界面。下面是一个使用 React.lazy()Suspense 组件的简单例子:

import React, { lazy, Suspense } from 'react';

const MyComponent = lazy(() => import('./MyComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <MyComponent />
      </Suspense>
    </div>
  );
}

在上面的代码中,当 <MyComponent /> 组件被渲染时,React 将自动异步加载 MyComponent 模块。fallback 属性指定了当组件加载时显示的 loading 界面。当组件加载完成时,React 将自动将其呈现。

2. 异步列表组件示例

下面我们通过一个「异步列表」组件示例来演示 React.lazy() 和 Suspense,先看最终的效果:

演示效果

2.1 父组件 ArtistPage

我们通过一个父组件 ArtistPage 和一个子组件 Albums 来实现上面 gif 中的效果。父组件 ArtistPage 实现思路如下:

  1. 页面加载完成后,默认渲染「Open The Beatles artist page」按钮,如果按钮被点击则展示列表数据;
  2. 在展示列表数据前,首先通过 Suspense 展示一个过渡用的 Loading 页面,当子组件和其中的数据请求完成后再展示列表数据。

父组件 ArtistPage 代码如下:

import { Suspense, useState } from "react";

import Albums from "@/components/Albums";

export default function ArtistPage() {
  const [show, setShow] = useState(false);

  // 定义组件的状态 show,控制组件的渲染
  // 初始时设置为 false,点击按钮后设置为 true
  // 以展示 The Beatles 的专辑页面
  if (show) {
    // 如果 show 为 true,则渲染以下内容
    return (
      <>
        <h1>The Beatles</h1>
        {/* 使用 React Suspense 包装组件,添加 fallback UI */}
        <Suspense fallback={<Loading />}>
          {/* 传入 artistId 属性,渲染 The Beatles 的专辑 */}
          <Albums artistId="the-beatles" />
        </Suspense>
      </>
    );
  } else {
    // 如果 show 为 false,则渲染以下按钮
    return (
      <button onClick={() => setShow(true)}>
        Open The Beatles artist page
      </button>
    );
  }
}

// 定义 Loading 组件,用于在数据加载时显示
function Loading() {
  return <h2>🌀 Loading...</h2>;
}

当用户点击「Open The Beatles artist page」按钮时,组件的状态会更新,页面上会渲染出 The Beatles 的列表。在这个页面中,通过 React Suspense 和组件 Albums 来展示 The Beatles 的列表数据。Suspense 组件可以处理异步加载数据时的等待状态,并展示 fallback 属性指定的组件。在上面的父组件 ArtistPage 中,fallback 展示了一个 Loading 组件,用来展示数据加载过程中的等待状态。

2.2 子组件 Albums

我们将根据上面父组件 ArtistPage 传入的参数 articleId 字段来渲染子组件数据列表。示例代码如下:

import { fetchData } from "./data";

// 注意:这个组件使用的是一个实验性的 API,在 React 的稳定版本中尚未可用。
// 如果你想要一个实际的示例,可以尝试使用与 Suspense 集成的框架,例如 Relay 或 Next.js。

// Albums 组件,接收一个字符串类型的 artistId 参数
export default function Albums({ artistId }) {
  // 通过fetchData函数获取artistId下的专辑数据,使用use函数包装
  const albums = use(fetchData(`/${artistId}/albums`));
  return (
    // 渲染专辑列表
    <ul>
      {albums.map((album) => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

// 这是一个临时函数,用于让这个演示程序正常运行。
// TODO:用真正的实现替换此函数。
function use(promise) {
  // 如果 promise 状态已经是已完成,则返回结果
  if (promise.status === "fulfilled") {
    return promise.value;
  } 
  // 如果 promise 状态是拒绝,则抛出异常
  else if (promise.status === "rejected") {
    throw promise.reason;
  } 
  // 如果 promise 状态是挂起,则抛出异常
  else if (promise.status === "pending") {
    throw promise;
  } 
  // 如果 promise 状态未知,则先设置状态为挂起,然后等待 promise 结果
  else {
    promise.status = "pending";
    promise.then(
      (result) => {
        promise.status = "fulfilled";
        promise.value = result;
      },
      (reason) => {
        promise.status = "rejected";
        promise.reason = reason;
      }
    );
    throw promise;
  }
}

上面代码中的 use 函数是用来模拟请求异步数据用的,模拟展示获取数据的过程。如果你有 Express 或 Koa 的基础,可以在本地搭建一个简易的后端服务,将 use 函数的逻辑替换成真实的异步请求即可。这里的 use 方法会将数据获取到,然后把返回的数据渲染到一个无序列表中。在这一过程中,Suspense 会在数据未加载完成时展示父组件的 fallback 属性中的组件。fallback 中的组件是 Loading 组件。

最终的效果便是 gif 展示的样子。

演示效果

3. 总结

动态导入组件是一种优化 React 应用程序性能的有效方法,可以让我们根据需要按需加载组件,而不是在应用程序加载时将所有组件都打包到一个文件中。使用 React.lazy()Suspense 组件,我们可以很轻松地实现动态导入组件。不过,虽然动态导入组件可以提高我们程序的性能,但还是有一些事项需要注意:

  • React.lazy() 只适用于默认导出组件。如果要按需加载命名导出的组件,你需要使用普通的动态导入语法。
  • 由于动态导入组件需要进行网络请求,因此可能会对性能产生一定影响。因此,最好将动态导入组件与代码分割技术一起使用,以便只加载需要的代码。
  • 在使用 React.lazy() 时,我们必须要使用 Suspense 组件。如果没有使用 Suspense 组件,则可能会出现渲染问题。
  • 在某些情况下,React.lazy() 可能无法正常工作。例如,如果要动态导入的组件位于 node_modules 目录中,则可能无法正常工作。此时,我们需要使用其他的按需加载组件的方法,例如使用第三方库 loadable-components
  • 如果项目中使用了 Webpack 4 或更高版本,那么我们可以结合 webpackChunkName 使用 React.lazy(),这样可以为动态加载的代码块提供更有意义的名称。比如:

    const MyComponent = lazy(
      () => import(/* webpackChunkName: "my-component" */ "./MyComponent")
    );

    在上面的代码中,我们为导入的代码块提供了一个名为 "my-component" 的名称。这个名称是生成的代码块的名称,以便我们更容易地识别和调试。

相信通过上面的示例你应该了解了 React.lazy() 和 Suspense,熟练掌握它们是 React 开发中进阶中高级所必须得。如果对你有所启发,记得点个关注哦~

关注领福利

Last modification:August 15, 2024
如果觉得我的文章对你有用,请随意赞赏