基于React.Suspense和React.lazy的前端性能优化


文章放在库存很久了。。。

React16.6于2018年10月份发布,该版本带来了许多新的特性同时赋予给React更强大的功能。其中最为显著的两个特性是 React.SuspenseReact.lazy。这两个特性,将React的代码分割和懒加载带到了一个新的高度。使用这两个特性,你能够作到的是在真正须要时才加载该组件的文件。前端

本文主要介绍我在项目中如何使用 React.SuspenseReact.lazy以及该特性给咱们React开发者带来的好处。react

1、为何要使用代码分割

随着前端技术的不断发展,ES6模块、Babel转换、webpack打包等新技术的出现,前端应用如今彻底可使用模块化的方式完成,便于维护。 一般状况下,咱们会将全部的模块打包到一个文件中,当请求网页时加载该文件以展现整个应用。可是,随着网页功能的不断扩展,这便带来了网页加载缓慢、交互卡顿等问题,使用户体验很是糟糕。 致使这一问题的主要缘由是,咱们在页面加载时,会一次性加载全部代码,不管是当前要用的代码仍是以后用到的代码。可是用户在第一次进来时并不会用到全部的功能,所以 code-splitting即代码分割这个名词出现了。webpack

像webpack便提供了代码分割的功能。webpack中对代码分割的定义以下:git

Code splitting is one of the most compelling features of webpack. This feature allows you to split your code into various bundles which can then be loaded on demand or in parallel.github

意思就是说能够将代码拆分为多个bundle,同时能够按需或者并行加载。所以,为了提升应用的性能,咱们能够尝试如何合理的分割代码并延迟加载。web

2、如何分割代码?

一、动态加载

ES6标准引入了import以方便咱们静态加载模块。形式如:浏览器

import xxx from xxx.js复制代码

尽管import对于咱们加载模块颇有帮助,可是静态加载模块的方式必定程度上限制了咱们来实现异步模块加载。不过,目前动态加载模块的import()语法已处于提案阶段,而且webpack已将他引入并使用。import()提供了基于Promise的API,所以,import()的返回值是一个完成状态或拒绝状态的Promise对象。形式如:bash

import(/* webpackChunkName: 'module'*/ "module")
    .then(() => {
        //todo
    })
    .catch(_ => console.log('It is an error'))
复制代码

webpack在编译时,识别到动态加载的import语法,则webpack会为当前动态加载的模块建立一个单独的bundle。若是你使用的是官方的Create-react-app脚手架或React的服务端渲染框架Next.js,那么能够直接使用动态import语法。若是你的脚手架是你本身配置的webpack,那么你须要按照官方指南来设置,请移步[1]。网络

二、动态加载React组件

当前最为流行的一种方法是使用 React-loadable[2]库提供的懒加载React组件。它利用import()语法,使用Promise语法加载React组件。同时,React-loadable支持React的服务端渲染。 一般,咱们以以下方式实现组件:app

import LazyComponet from 'LazyComponent';

export default function DemoComponent() {
    return (
        <div>
            <p>demo component</p>
            <AComponent />
        </div>
    )
}
复制代码

在上面的例子中,假设 LazyComponetDemoComponent渲染时咱们并不展现。可是由于咱们使用import语法将 LazyComponet导入,因此在编译时会将 LazyComponet的代码与 DemoComponent的代码打包到同一个bundle里面。 可是,这并非咱们想要的。因此咱们能够经过使用 React-loadable来懒加载 LazyComponet,同时将 LazyComponet的代码单独打包到一个bundle里面。咱们能够看一下官网提供的例子:

import Loadable from 'react-loadable';
import Loading from './my-loading-component';

const LoadableComponent = Loadable({
  loader: () => import('./my-component'),
  loading: Loading,
});

export default class App extends React.Component {
  render() {
    return <LoadableComponent/>;
  }
}
复制代码

从例子中咱们能够看到,react-loadable使用动态import()方法,并将导入的组件分配给loader属性。同时,react-loadable提供了一个loading属性,以设置在加载组件时将展现的组件。

3、React.lazy和React.suspense的使用

React.lazy and Suspense is not yet available for server-side rendering. If you want to do code-splitting in a server rendered app, we recommend Loadable Components. It has a nice guide for bundle splitting with server-side rendering.

在使用以前,咱们须要特别注意的一点是,React官方明确支持,React.lazy和Suspense并不支持服务端渲染。所以,使用服务端渲染的同窗,请绕行至 react-loadableloadable-components[3]。

因为我是对原有项目进行的升级,所以,本文如下内容主要针对于老项目升级React最新版所作的工做。

一、代码升级React最新版

若是你的代码是Reactv16,那么能够直接升级到最新版本,固然React16.6已经提供了lazy和suspense方法。若是是v16以前,则按照官方操做迁移。

二、肯定原有代码的懒加载组件

首先,按照需求,将非首屏加载的组件肯定为懒加载组件,个人项目共肯定五个组件能够进行懒加载。修改方式很简单,原有引入组件的方法为:

import LazyComponent from "../components/LazyComponent ";复制代码

修改成:

const LazyComponent = React.lazy(() =>
  import(/* webpackChunkName: 'lazyComponent'*/ "../components/LazyComponent")
);
复制代码

如代码所示:将静态引用组件的代码替换为调用React.lazy(),在lazy()传入一个匿名函数做为参数,在函数中动态引入 lazyComponent组件。这样在咱们渲染这个组件前,浏览器将不会下载 lazyComponent.bundle.js文件和它的依赖。 其中,import内的webpackChunkName为咱们定义的bundle文件名。

若是React要渲染组件时,组件依赖的代码还没下载好,会出现什么状况? <React.Suspense/>的出现帮咱们解决问题。在代码未下载好前,它将会渲染fallback属性传入的值。所以咱们的原有代码为:

return (<div><MainComponet /><LazyComponent /></div>)复制代码

修改成:

return (
        <div>
            <MainComponet />
            <React.Suspense fallback={<div>正在加载中</div>}>
                <LazyComponent />
            </React.Suspense>
        </div>
    )
复制代码

fallback中能够修改成任意的spinner,本次不作过多优化。假如你不使用React.suspense,则React会给出你错误提示,所以记得React.lazy和React.Suspense搭配使用。 此时咱们能够看一下咱们的网络请求。

enter image description here

从图中咱们能够看到,咱们动态加载的lazyComponet组件被单独打包到一个bundle里面,然而,在首屏加载的时候,该bundle已经加载到咱们的页面中了,这也许并非咱们想要的,咱们想要的是当咱们须要的时候再加载。接下来咱们就控制一下,当咱们须要的时候,再加载该文件。

三、经过变量控制加载

本来我选择的五个懒加载组件均属于弹层性质的组件,所以必然会设置一个state来控制该组件的显示与隐藏,所以咱们将代码改成:

return (
        <div>
            <MainComponet />
            {this.state.showLazyComponent && (
                <React.Suspense fallback={<div>正在加载中</div>}>
                    <LazyComponent />
                </React.Suspense>
            )}

        </div>
    )
复制代码

此时咱们能够看一下网络请求。

enter image description here

从图中能够看出,在首屏加载时,并未加载咱们的懒加载组件 LazyComponent所对应的bundle包。等到咱们点击须要该组件显示时,页面才去加载该js。这便达到了咱们代码分离并懒加载的目的。那么咱们这么操做,到底主bundle包的体积减小了吗?接下来咱们打包文件看一下。

四、打包文件

拆分前打包出来的文件:

Alt text

拆分后打包出来的文件:

Alt text

app.js文件变小,随之增长 lazyComponent.js。当懒加载组件多时,咱们即可必定程度上减小首屏加载文件的大小,提升首屏的渲染速度。

4、验证优化的有效性

一、利用Puppeteer和Performance API作对比

为了验证前面我所作的优化的有效性,我作了一组对比实验。实验内容为使用puppeteer分别访问优化前和优化后的页面1000次,使用Performance API分别统计五项数据在这1000次访问时的平均值。 实验结果以下图所示,其中:

  • A为request请求平均耗时

  • B为解析dom树耗时平均耗时

  • C为请求完毕至DOM加载平均耗时

  • D为请求开始到domContentLoadedEvent结束平均耗时

  • E为请求开始至load平均耗时 Alt text

折线图没法准确展现数据,所以,附表格数据以下:

类别 优化后 优化前
A(request请求平均耗时) 7.013 7.042
B(解析dom树耗时平均耗时) 30.284 32.597
C(请求完毕至DOM加载平均耗时) 552.861 582.01
D(请求开始到domContentLoadedEvent结束平均耗时) 569.137 589.07
E(请求开始至load平均耗时) 1055.597 1126.941

从数据中咱们能够看出,优化先后请求时间并无什么影响,可是整体load的时间明显缩短并立刻进入1000ms大关,可见优化后对于首屏的加载速度仍是有明显提高。

注:因puppeteer运行1000次的过程当中,会出现网络波动的状况,致使有些请求的数据偏大,所以平均值并不能彻底体现正常的请求时间。但1000次的平均值足以进行优化先后的请求时间对比。

二、利用Chorme Performance 参数作对比

由于Performance API提供的参数有限,所以我从Chrome浏览器的performance summary中拿到了单次页面请求时的参数。由于是单次数据,所以咱们不进行详细的对比。在此列出,只为说明优化先后浏览器渲染时间上哪些部分有提高。

 优化前:

Alt text

优化后:

Alt text

  • 蓝色:加载(Loading)时间下降

  • 黄色:脚本运算(Scripting)时间下降

  • 紫色:渲染(Rendering)时间下降

  • 绿色:绘制(Painting)时间持平

  • 灰色:其余(Other)时间下降

  • 闲置:浏览器空闲时间下降

另外,我从Network中发现,优化后由于页面解析的相对以前较快,所以主接口的请求时间也相应的提早了一些。

5、总结

从多项数据代表, React.lazyReact.Suspense的使用必定程度上加快了首屏的渲染速度,使得咱们的页面加载更快。 另外,当咱们想添加一个新功能而引入一个新依赖时,咱们每每会评估该依赖的大小以及引入该依赖会对原有bundle形成多大影响。假如该功能不多被用到,那么咱们能够痛快地使用 React.lazyReact.Suspense来按需加载该功能,而无需牺牲用户体验了。

6、扩展阅读

[1] https://webpack.js.org/guides/code-splitting/ 

[2] https://github.com/jamiebuilds/react-loadable 

[3] https://github.com/smooth-code/loadable-components

相关文章
相关标签/搜索