如何用Next.js进行动态导入和代码拆分(附代码)

2,154 阅读7分钟

简介

在生产环境中优化性能有时是一项艰难的任务。对网站性能的微调是不容忽视的,因为它的缺点是网页速度慢,用户体验差。这些网站往往加载缓慢,图片渲染缓慢,从长远来看,会导致网站访问者的跳出率增加,因为大多数用户不愿意等待内容弹出。

在本教程中,我们将介绍在Next.js应用程序中加快网站性能的不同模式。

目标和先决条件

在本文结束时,读者将清楚地了解如何在Next.js网络应用中实现性能最大化。

要跟上这篇文章,需要事先了解Next.js框架。

什么是动态导入和代码拆分?

动态导入,也被称为代码拆分,指的是将一捆捆的JavaScript代码分割成更小的块,然后将其拼凑在一起并加载到应用程序的运行时间中,以此来大幅提升网站性能的做法。

它是作为JavaScript静态导入的升级版而开发的,静态导入是在JavaScript模块的顶层使用导入语法为模块或组件添加导入的标准方式。

虽然这是一种常用的方法,但在性能优化方面存在一些缺点,特别是在以下情况下:

  • 大型代码库,由于构建过程将所有需要的文件编译成一个单一的捆绑包,所以会产生较大的捆绑包尺寸,导致加载时间延长
  • 需要某些用户操作的网页,如点击导航菜单项来触发页面加载。在这里,所需的页面只有在满足导航条件时才会呈现,当组件被静态导入时,可能会触发缓慢的初始页面加载

动态导入与静态导入有何不同?

与静态导入不同,动态导入通过应用一种被称为代码分割的方法来工作。代码拆分是将代码分为不同的包,这些包使用树形格式平行排列,其中的模块是动态加载的--模块只有在需要时才被导入并包含在JavaScript包中。代码被分割得越多,包的大小就越小,页面的加载速度就越快。

这种方法创建了多个捆绑包,在网页运行时动态加载。动态导入利用了写为内联函数调用的导入语句。

让我们来看看一个比较。假设我们希望在我们的应用程序中导入一个导航组件;下面是一个导航组件的静态和动态导入的例子。

静态导入:

import Nav from './components/Nav'
export default function Home() {
  return (
    <div>
      <Nav/>
    </div>
  )
}

动态导入:

import dynamic from "next/dynamic";
import { Suspense } from "react";
export default function Home() {
  const Navigation = dynamic(() => import("./components/Nav.js"), {
    suspense: true,
  });
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <Navigation />
      </Suspense>
    </div>
  );
}

这里,导航组件的相对部分在import() 块中指定。注意,next/dynamic 不允许在import() 的参数中使用模板字面或变量。

另外,react/suspense 有一个指定的回退元素,在导入的组件可用之前,该元素会被显示。

Next.js中动态导入的好处

因实施动态导入而优化网站性能,又会带来以下网站的好处:

  • 更快的页面加载:网站加载和显示内容的速度是至关重要的,因为你的受众希望快速完成工作,不会为缓慢的网页停留。
    • 动态导入对图像加载时间也有积极影响
  • 低跳出率:跳出率,指的是用户在没有与网站互动的情况下退出你的网页的比率,通常表明(并且是由)缓慢的加载时间造成的。较低的跳出率通常意味着更快的网站性能
  • 改进网站互动时间:这涉及到TTI,即互动时间,即用户要求采取行动和用户得到结果之间的时间间隔。这些互动可以包括点击链接、滚动页面、在搜索栏中输入信息、向购物车中添加物品等。
  • 更好的网站转换率:随着更多的用户从使用一个优化良好的网站中获得满足感,他们将更有可能进行转换

有了所有这些好处,你可能正在考虑如何在你的应用程序中使用动态导入。那么,最大的问题是,我们如何在Next.js应用程序中实现动态导入和代码拆分?下一节将介绍如何实现这一目标的详细步骤。

在Next.js中实现动态导入和代码拆分

Next.js通过next/dynamic 模块,可以轻松地在Next应用程序中创建动态导入,如上图所示。next/dynamic 模块实现了React组件的懒惰加载导入,并且是建立在React Lazy之上。

它还利用了React Suspense库,允许应用程序推迟加载组件,直到它们被需要,从而提高初始加载性能,因为更轻的JavaScript构建。

动态导入命名的出口

在本文早些时候,我们演示了使用next/dynamic 来导入一个组件。但我们也可以为从另一个文件导出的函数或方法做动态导入。这一点演示如下:

import React from 'react'

export function SayWelcome() {
  return (
    <div>Welcome to my application</div>
  )
}
const SayHello = () => {
  return (
    <div>SayHello</div>
  )
}
export default SayHello

在上面的代码中,我们有一个组件,SayHello ,和一个命名导入,SayWelcome 。我们可以为SayWelcome 方法做一个动态显式导入,如下所示:

import dynamic from "next/dynamic";
import { Suspense } from "react";
export default function Home() {
  const SayWelcome = dynamic(
    () => import("./components/SayHello").then((res) => res.SayWelcome)
  );
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <SayWelcome />
      </Suspense>
    </div>
  );
}

上面的代码导入了SayHello 组件,然后从响应中返回SayWelcome 输出。

动态导入多个组件

假设我们有UserDetailsUserImage 组件。我们可以导入并显示这两个组件,如下所示:

import dynamic from 'next/dynamic'

const details = dynamic(() => import('./components/UserDetails'))
const image = dynamic(() => import('./components/UserImage'))

function UserAccount() {
  return (
    <div>
      <h1>Profile Page</h1>
      <details />
      <image />
    </div>
  )
}

const App = () => {
  return (
    <>
      <UserAccount />
  )
    </>
}

export default App

在上面的代码中,我们为UserDetailsUserImage 组件添加了动态导入,然后我们把这些组件放在一起,成为一个单一的组件,UserAccount 。最后,我们在应用程序中返回了UserAccount 组件。

用于客户端渲染的动态导入

通过next/dynamic 模块,我们还可以禁用导入组件的服务器端渲染,而在客户端渲染这些组件。这特别适用于那些不需要太多用户互动或有外部依赖的组件,如API。这可以通过在导入组件时将ssr 属性设置为false 来实现:

import dynamic from 'next/dynamic'

const HeroItem = dynamic(() => import('../components/HeroItem'), {
  ssr: false,
})

const App = () => {
  return (
    <>
      <HeroItem />
  )
    </>
}

在这里,HeroItem 组件的服务器端渲染设置为false ,因此它在客户端进行渲染。

库的动态导入

除了导入本地组件之外,我们还可以为外部依赖添加动态导入。

例如,假设我们希望使用Axiosfetch,在用户请求时从API获取数据。我们可以为Axios 定义一个动态导入并实现它,如下所示:

import styles from "../styles/Home.module.css";
import { React, useState } from "react";

export default function Home() {
  const [search, setSearch] = useState("");
  let [response, setResponse] = useState([]);
  const api_url = `https://api.github.com/search/users?q=${search}&per_page=5`;

  return (
    <div className={styles.main}>
      <input
        type="text"
        placeholder="Search Github Users"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />

      <button
        onClick={async () => {
          // dynamically load the axios dependency
          const axios = (await import("axios")).default;
          const res = await axios.get(api_url).then((res) => {
            setResponse(res);
          });

        }}
      >
        Search for GitHub users
      </button>

      <div>
        <h1>{search} Results</h1>
        <ul>
          {response?.data ? (
            response && response?.data.items.map((item, index) => (
              <span key={index}>
                <p>{item.login}</p>
              </span>
            ))
          ) : (
            <p>No Results</p>
          )}
        </ul>
      </div>
    </div>
  );
}

在上面的代码中,我们有一个输入字段来搜索GitHub上的用户名。我们使用useState() Hook来管理和更新输入字段的状态,我们设置了Axios 的依赖关系,以便在点击Search for GitHub users 按钮时动态导入。

当响应返回时,我们对其进行映射,并显示五个用户的usernames ,这些用户的名字与输入字段中的搜索查询相对应。
下面的GIF演示了上述代码块的操作:

Dynamically importing libraries to a Next.js app

结论

在这篇文章中,我们了解了动态导入/代码拆分,它的优点,以及如何在Next.js应用程序中使用它。

总的来说,如果你想缩短网站的加载时间,动态导入和代码拆分是一个必须的方法。如果你的网站有图片,或者要显示的结果取决于用户的互动,那么动态导入将大大提升网站的性能和用户体验。