是否应该延迟加载React组件,这么做是否可以提升应用的性能?我猜你应该想知道的吧,让我们开始吧
React非常快,然而再快它也有个加载的过程,在页面呈现出来之前浏览器还有很多的工作要做
React的瓶颈之一就是 bundle 文件的大小,庞大的 bunle 文件所带来的问题是会导致 TTI 时间变长
什么是TTI?
TTI,time to interactive,测量页面从开始加载到主要资源完成渲染,并能够快速、可靠地响应用户输入所需的时间。它可以用毫秒、秒、分钟等时间单位来衡量
让我们来看一下掘金在低速3G网络状态下的TTI,如下图:
在这里你可以看到下载并执行了哪些 js 文件,花了多长时间
React + Webpack = 一个很大的bundle文件
99%的情况下,当你在React中开发时,你会使用Webpack来帮助你将所有东西打包成一个漂亮的包。
Webpack的核心是帮助你在开发过程中进行热重新,并将所有JavaScript文件捆绑到一个或多个JS文件中。
如果你开发的是单页应用程序(SPA),你通常会有一个JavaScript bundle文件。
如上图所示,你的 React 文件不是很大,甚至可能是最小的,但是由于你安装了 React 核心库以及一些其它的第三方库,导致你的 bundle 输出文件变得非常大。
懒加载 React 组件的好处
懒加载 React 组件的概念其实非常简单,渲染一个页面的时候尽可能的加载更少的代码,或者在需要时加载额外的小块代码。
页面加载的代码量小了,性能就能得到提升,TTI也就变短了,页面将能更快的呈现出来。
使用 React lazy 和 Suspense 实现懒加载
在我们开始之前,我们先假设当前已经有一个应用程序,这个应用有3个页面:猫的列表,添加猫的表单,猫详情页面
让我们来看一下代码:
以上代码是一个路由配置文件,下边这个文件是 App.js,它引入了路由并定义了输出,让我们看一下31-44行
通过 Array.map 循环来创建路由组件,我们来看一眼 React 开发者工具,看看它是怎么样来初始化渲染器的
可以看到 React 渲染了每一个页面路由,即便此时此刻你用不上,也把它渲染出来了
怎么给 React 路由添加懒加载呢
首先移除 routes.js 文件,然后修改 App.js,看下边代码的高亮部分:
高亮部分展示了 App.js 是怎么修改的
第1步:导入 Switch 组件
看一下 App.js 第 5 行,我从react-router-dom中导入了导入了Switch,Switch组件用来渲染单一的路由组件
在上边那张 React 开发者工具截图中,你看到3个路由,你现在再看,已经只有一个路由了
这是很有帮助的,因为在有限的时间内,我们不需要加载那些用不到的代码。
第2步:创建 React lazy 组件
在第8-10行,我为3个页面分别创建了一个 React lazy 组件
const CatList = React.lazy(() => import('./pages/CatList'));
React lazy 让我们动态导入一个文件并且把它转化为一个常规组件。
第3步:使用 React Suspense 组件
在使用 React lazy 组件之前,我们应该先使用 React.Suspense 把路由包起来
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} />} />
最终结果
我们刚刚所做的,最终把一个应用拆成了更小的代码块
如图所示,生成了一个主要的 bundle js 文件(main.js),但是每个页面只会下载一个 chunk 文件。如果用户点击跳转到其它页面,将会根据各自需要下载相应的 chunk 文件。
这样会使得浏览器加载速度更快,更小的 chunk 更快的 TTI
总结
在 React 中应用代码分割技术会带来更好的性能,因为每次加载一个页面的时候它会加载更少的代码,将会带来更好的用户体验。
参考文章:Code splitting routers with React Lazy and Suspense