使用 React lazy 和 Suspense 实现根据路由进行代码分割

597 阅读4分钟

是否应该延迟加载React组件,这么做是否可以提升应用的性能?我猜你应该想知道的吧,让我们开始吧

React非常快,然而再快它也有个加载的过程,在页面呈现出来之前浏览器还有很多的工作要做

React的瓶颈之一就是 bundle 文件的大小,庞大的 bunle 文件所带来的问题是会导致 TTI 时间变长

什么是TTI?

TTI,time to interactive,测量页面从开始加载到主要资源完成渲染,并能够快速、可靠地响应用户输入所需的时间。它可以用毫秒、秒、分钟等时间单位来衡量

让我们来看一下掘金在低速3G网络状态下的TTI,如下图:

image.png

在这里你可以看到下载并执行了哪些 js 文件,花了多长时间

React + Webpack = 一个很大的bundle文件

99%的情况下,当你在React中开发时,你会使用Webpack来帮助你将所有东西打包成一个漂亮的包。

Webpack的核心是帮助你在开发过程中进行热重新,并将所有JavaScript文件捆绑到一个或多个JS文件中。

如果你开发的是单页应用程序(SPA),你通常会有一个JavaScript bundle文件。

image.png

如上图所示,你的 React 文件不是很大,甚至可能是最小的,但是由于你安装了 React 核心库以及一些其它的第三方库,导致你的 bundle 输出文件变得非常大。

懒加载 React 组件的好处

懒加载 React 组件的概念其实非常简单,渲染一个页面的时候尽可能的加载更少的代码,或者在需要时加载额外的小块代码。

页面加载的代码量小了,性能就能得到提升,TTI也就变短了,页面将能更快的呈现出来。

使用 React lazy 和 Suspense 实现懒加载

在我们开始之前,我们先假设当前已经有一个应用程序,这个应用有3个页面:猫的列表,添加猫的表单,猫详情页面

image.png

让我们来看一下代码:

image.png

以上代码是一个路由配置文件,下边这个文件是 App.js,它引入了路由并定义了输出,让我们看一下31-44行

image.png

通过 Array.map 循环来创建路由组件,我们来看一眼 React 开发者工具,看看它是怎么样来初始化渲染器的

image.png

可以看到 React 渲染了每一个页面路由,即便此时此刻你用不上,也把它渲染出来了

怎么给 React 路由添加懒加载呢

首先移除 routes.js 文件,然后修改 App.js,看下边代码的高亮部分:

image.png

高亮部分展示了 App.js 是怎么修改的

第1步:导入 Switch 组件

看一下 App.js 第 5 行,我从react-router-dom中导入了导入了SwitchSwitch组件用来渲染单一的路由组件

在上边那张 React 开发者工具截图中,你看到3个路由,你现在再看,已经只有一个路由了

image.png

这是很有帮助的,因为在有限的时间内,我们不需要加载那些用不到的代码。

第2步:创建 React lazy 组件

在第8-10行,我为3个页面分别创建了一个 React lazy 组件

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

React lazy 让我们动态导入一个文件并且把它转化为一个常规组件。

第3步:使用 React Suspense 组件

在使用 React lazy 组件之前,我们应该先使用 React.Suspense 把路由包起来

image.png

React.Suspense 组件提供了一个 fallback 属性,通过它可以知道当前页面正在加载

以上就是动态导入的过程

那么问题来了,什么是动态导入?

// 静态导入
import React from 'react';

// 动态导入
import('./path/to/component');

我们来看一下上面这两种使用import导入的示例,看起来很像,实则不然。

第一个 import 语句只允许出现在页面顶部,并且 from 后只能接受字符串,这对于你导入需要的代码模块非常方便。

第二个 import 语句,使用了圆括号,就好像使用 function 一样,这会让 Javascript 引擎知道这是要被异步处理的,它将会返回一个 promise。

因为动态导入是异步的,异步加载期间 React.Suspense 开始发挥作用了。Suspense 将会展示 fallback 里的内容,一直到 promise 完成为止。

第4步:添加 React lazy 组件到路由上

<Route exact path="/" render={() => <CatList cats={cats}/>} />
<Route path="/add" render={props => {
  return <AddCat onSubmit={cat => {
    setCats([...cats, cat])
    props.history.push('/')
  }} />
}} />
<Route exact path="/cat/:name" render={() => <SingleCat cats={cats} />} />

最终结果

我们刚刚所做的,最终把一个应用拆成了更小的代码块

image.png

如图所示,生成了一个主要的 bundle js 文件(main.js),但是每个页面只会下载一个 chunk 文件。如果用户点击跳转到其它页面,将会根据各自需要下载相应的 chunk 文件。

image.png

这样会使得浏览器加载速度更快,更小的 chunk 更快的 TTI

总结

在 React 中应用代码分割技术会带来更好的性能,因为每次加载一个页面的时候它会加载更少的代码,将会带来更好的用户体验。

参考文章:Code splitting routers with React Lazy and Suspense