Next.js 中使用动态导入进行代码拆分

如何通过代码拆分和智能加载策略加速您的 Next.js 应用程序。

您将学到什么?

这篇文章解释了不同类型的 代码拆分,以及如何使用动态导入来加速您的 Next.js 应用程序。

基于路由和基于组件的代码拆分

默认情况下,Next.js 会将您的 JavaScript 代码拆分成每个路由的单独 chunk。当用户加载您的应用程序时,Next.js 只发送初始路由所需的代码。当用户在应用程序中导航时,他们会获取与其他路由关联的 chunk。基于路由的代码拆分最大限度地减少了需要一次性解析和编译的脚本量,从而加快了页面加载速度。

虽然基于路由的代码拆分是一个很好的默认设置,但您可以使用组件级别的代码拆分进一步优化加载过程。如果您的应用程序中有大型组件,将它们拆分成单独的 chunk 是一个好主意。这样,任何非关键或仅在特定用户交互(例如单击按钮)时渲染的大型组件都可以延迟加载。

Next.js 支持动态 import(),这允许您动态导入 JavaScript 模块(包括 React 组件)并将每个导入作为单独的 chunk 加载。这为您提供了组件级别的代码拆分,并使您能够控制资源加载,以便用户仅下载他们正在查看的站点部分所需的代码。在 Next.js 中,这些组件默认情况下是服务器端渲染 (SSR) 的。

动态导入的实际应用

这篇文章包含一个示例应用程序的几个版本,该应用程序由一个带有一个按钮的简单页面组成。当您浏览应用程序的每个版本时,您将了解动态导入与静态导入的不同之处以及如何使用它们。

在应用程序的第一个版本中,小狗位于 components/Puppy.js 中。为了在页面上显示小狗,应用程序使用静态导入语句在 index.js 中导入了 Puppy 组件

import Puppy from "../components/Puppy";

要查看 Next.js 如何打包应用程序,请在 DevTools 中检查网络跟踪

  1. 要预览网站,请按“View App”(查看应用)。然后按“Fullscreen”(全屏) fullscreen

  2. 按 `Control+Shift+J`(或 Mac 上的 `Command+Option+J`)打开 DevTools。

  3. 点击“Network”(网络)选项卡。

  4. 选中“Disable cache”(禁用缓存)复选框。

  5. 重新加载页面。

当您加载页面时,所有必要的代码,包括 Puppy.js 组件,都打包在 index.js

DevTools Network tab showing showing six JavaScript files: index.js, app.js, webpack.js, main.js, 0.js and the dll (dynamic-link library) file.

当您按下“Click me”(点击我)按钮时,只有对小狗 JPEG 的请求被添加到“Network”(网络)选项卡中

DevTools Network tab after the button click, showing the same six JavaScript files and one image.

这种方法的缺点是,即使用户不单击按钮查看小狗,他们也必须加载 Puppy 组件,因为它包含在 index.js 中。在这个小例子中,这没什么大不了的,但在实际应用程序中,仅在必要时加载大型组件通常是一个巨大的改进。

现在查看应用程序的第二个版本,其中静态导入被动态导入替换。Next.js 包含 next/dynamic,这使得可以为 Next 中的任何组件使用动态导入

import Puppy from "../components/Puppy";
import dynamic from "next/dynamic";

// ...

const Puppy = dynamic(import("../components/Puppy"));

按照第一个示例中的步骤检查网络跟踪。

当您首次加载应用程序时,仅下载 index.js。这次它小了 0.5 KB(从 37.9 KB 降至 37.4 KB),因为它不包含 Puppy 组件的代码

DevTools Network showing the same six JavaScript files, except index.js is now 0.5 KB smaller.

Puppy 组件现在位于一个单独的 chunk 中,即 1.js,仅当您按下按钮时才加载

DevTools Network tab after the button click, showing the additional 1.js file and the image added to the bottom of the file list.

在实际应用程序中,组件通常要大得多,延迟加载它们可以减少数百 KB 的初始 JavaScript 有效负载。

带有自定义加载指示器的动态导入

当您延迟加载资源时,最好提供一个加载指示器,以防出现任何延迟。在 Next.js 中,您可以通过为 dynamic() 函数提供一个额外的参数来做到这一点

const Puppy = dynamic(() => import("../components/Puppy"), {
  loading: () => <p>Loading...</p>
});

要查看加载指示器的实际效果,请在 DevTools 中模拟慢速网络连接

  1. 要预览网站,请按“View App”(查看应用)。然后按“Fullscreen”(全屏) fullscreen

  2. 按 `Control+Shift+J`(或 Mac 上的 `Command+Option+J`)打开 DevTools。

  3. 点击“Network”(网络)选项卡。

  4. 选中“Disable cache”(禁用缓存)复选框。

  5. 在“Throttling”(限速)下拉列表中,选择“Fast 3G”(快速 3G)。

  6. 按下“Click me”(点击我)按钮。

现在,当您单击按钮时,加载组件需要一段时间,并且应用程序在此期间会显示“Loading…”(正在加载…)消息。

A dark screen with the text

没有 SSR 的动态导入

如果您只需要在客户端渲染组件(例如,聊天小部件),您可以通过将 ssr 选项设置为 false 来做到这一点

const Puppy = dynamic(() => import("../components/Puppy"), {
  ssr: false,
});

结论

凭借对动态导入的支持,Next.js 为您提供了组件级别的代码拆分,这可以最大限度地减少您的 JavaScript 有效负载并缩短应用程序加载时间。所有组件默认情况下都是服务器端渲染的,您可以随时禁用此选项。