Last Surprise Lyn
  1. 1 Last Surprise Lyn
  2. 2 Time Bomb Veela
  3. 3 One Last You Jen Bird
  4. 4 Flower Of Life 发热巫女
  5. 5 Warcry mpi
  6. 6 Libertus Chen-U
  7. 7 Hypocrite Nush
  8. 8 The Night We Stood Lyn
  9. 9 Life Will Change Lyn
  10. 10 かかってこいよ NakamuraEmi
  11. 11 Quiet Storm Lyn
2018-09-16 18:05:39

React性能优化综述

相比于Vue的高度封装和自动化,React给予了开发者更高的自由度,但相对的,就要更注重性能优化相关的问题,这里就以React为入口总结一下前端应用开发中需要注意的一些常规性能优化点。

我们知道常规单页应用最终都会被webpack打包为若干js,css,入口html和各种静态资源,从http请求到入口html资源到整个应用准备完成经历了不少阶段,这篇文章就按照时间顺序分别介绍其中可优化的点。

HTML解析

首屏加载动画

单页应用的内容都是挂载到一个空白根节点,换句话说,在主体代码加载完成之前,首屏都不会渲染,用户体验很不友好,所以可以在这个根节点内设置一段CSS动画代码或者骨架屏,加快首屏渲染时间。

<div id="root">
    <!-- css或者svg相关动画 -->
</div>

通常来说入口文件越简洁越好,尽可能的减少手工代码,所以这一步最好的实现是通过html-webpack-plugin实现自动化。

如果对loading动画有更高的要求,比如要使用项目内通用的loading组件,可以借助prerender-spa-plugin在构建时就将组件代码转化为编译后代码并插入到入口html。

内联首屏CSS

CSS外链的请求和加载同样会阻塞首屏渲染,进而影响首屏时间,这里有两种解决方案,分别对应着两类CSS管理方案。

  • CSS不提取为外链,伴随着组件打包和缓存,那么只需要移除webpack的CSS提取插件即可。
  • CSS提取为外链,单独缓存,那么需要通过类似HTML Critical Webpack Plugin的插件,通过无头浏览器加载并提取首屏的CSS,与打包的CSS分离并内联到入口html。

资源外链加载

解析首屏HTML时,最消耗性能的地方应该就是项目打包文件的加载了。除了首屏的关键CSS外,通常CSS会打成一个文件,而JS会按照其作用分为如下几类:

  • 三方基础库,更新频率相当低
  • 浏览器兼容的polyfill,更新频率也很低
  • 业务通用封装代码,大部分业务代码会引用到的通用部分代码,更新频率相对高一些
  • 业务代码,具体的业务逻辑代码,更新频率高

根据以上这个内容文件的特点,也可以针对性的做出优化。

dllPlugin插件抽出三方基础库并实现长缓存

通过dllPlugin将三方基础库单独打包,并给主代码引用,由于三方基础库并不经常更新,如此做不仅可以加快打包速度,还可以针对三方包做长缓存。

动态polyfill

polyfill的特点在于并不是所有用户都需要,但是需要的用户的polyfill代码又是固定的,所以这部分最好的处理方式是针对特定用户加载其所需polyfill。这里的使用方式很简单,只需要外链一个JS即可。原理就是根据http请求的User Agent字段返回相对应的polyfill。

<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>

SplitChunksPlugin插件抽出业务通用封装代码

该插件是Webpack4用来替代之前版本的CommonChunksPlugin做公共部分代码提取的,相当强大,使用方式就不详细介绍了,简单来说就是根据不同chunk引用次数或者其他规则抽出公共部分,而不是在不同chunk内重复打包。

runtimeChunkPlugin抽出webpack连接各模块的代码

因为打包后并没有打包前的文件结构,webpack的打包代码是通过runtime代码以及其伴随的manifest数据来寻找打包后模块以及各种加载和解析逻辑的,而这部分代码和应用代码混合打包后,即使应用代码没有变化,打包后计算的hash也会变化因为每次构建runtime和manifest都会变化,这样不利于实现缓存。所以最佳做法是将这部分代码通过runtimeChunkPlugin提取并通过HtmlWebpackPlugin的周边插件内联到入口文件中避免请求。

Tree Shaking优化业务代码

Tree Shaking的本质就是在打包时将模块中未使用的导出部分添加一段未使用标记的魔法注释,并在unglify时识别这段魔法注释并移除这段导出,而实际上模块中的除了导出的代码仍然会被打包。

而从webpack4开始,Tree Shaking会识别npm包的package.json的sideEffects声明,如果为false会自动应用tree shaking,本质其实类似babel-plugin-import,就是由import {a} from x转化为import {a} from xx/a,同时也会把这个包的未引用模块从bundle中真正的移除,所以对那些确定无副作用的包添加sideEffects声明会极大的提高Tree Shaking效率。

动态加载编译后不同版本代码

通常我们会在生产环境下部署ES5版本的代码,但实际上目前绝大部分用户的浏览器都已经支持ES6的代码版本了,也就是说针对大多数用户都没必要编译到ES5加大代码体积。处理方法可以打包ES5和ES6两份代码,通过<script type="module">标签加载ES6版本的代码。

<script type="module" src="main.es6.js"></script>
<script nomodule src="main.es5.js"></script>

现代浏览器能识别module和nomodule,只加载main.es6.js的代码,而非现代浏览器同样不能识别这两个标签,只加载main.es5.js的代码。

预加载非首屏所需资源

使用preload和prefetch规则预加载即将用到的资源,之前写过一篇相关文章介绍用法。

服务端或浏览器端缓存

etag,service-worker等手段在http请求到收到响应的各个阶段部署缓存。

独立域名请求静态资源

对于静态资源来说发送同域下的cookie完全没有意义,只是无意义消耗带宽,并且浏览器对相同域名存在并发数限制,最好的做法还是对静态资源开启cdn缓存。

首屏内容渲染

HTML解析完成并且同步外链资源加载完毕后就进入首屏内容渲染阶段了,这部分主要工作就是根据应用逻辑初始化首屏所需的各种组件。

Code Splitting

应用代码如果不做任何优化就会打成一个bundle,而实际上首屏我们并不需要加载所有应用代码,将这个bundle切分为多个,并在需要的时候动态加载是更为理想的组织方式。

通常切分思路有两种,通常是按路由分割,更细粒度的是按组件内部的需求分割。webpack4已经支持动态import实现代码切分,使用上也相当简单,更重要的是理清切分的思路,具体到首屏只要配合路由加载首屏所需组件即可。

LazyLoad

这个也是老生常谈了,首屏不可见部分不加载,通常是针对静态资源,实现也比较简单,监听scroll事件或者使用IntersectionObserver监听元素可见性。

组件编写

PureComponent

由于React的响应式并不是类似于Vue的数据劫持自监听数据变化,所以组件是无法知道自己更新的最佳时机的,由此都需要针对性的优化shouldComponentUpdate方法,当然我们不可能针对每个组件都写一个特定的方法,所以比较通用的做法就是PureComponent了,其原理就是实现了shouldComponentUpdate对props和state变化前后做浅比较,不变则不重新渲染组件,大多数情况下我们都应该使用PureComponent。

StatelessComponent

对于不需要维护state的组件,大多数情况下纯函数组件都是被推荐的写法,但这并不代表纯函数组件是性能最好的组件。实际上纯函数组件在react中会被包装成只有一个render方法的内部组件,也就意味着所有情况下该组件都会重渲染,因此逻辑过于复杂或者的组件即使符合纯函数组件的条件,也应该考虑使用PureComponent。

Immutable

对于常规组件,PureComponent就能很好的减少更新频率,但复杂项目中,难免要维护一些浅比较无法胜任的复杂状态,而如果对这些复杂状态进行深度比较避免更新,收益还不一定大过比较耗时。这种情况就可以immutable化prop和state,具体使用就不在这里介绍了,大型复杂项目适合引入ImmutableJS,简单项目可以考虑更轻量级的seamless-immutable。

reselect和webworker

当然大部分情况下状态都是通过某个状态管理集中管理并分发的,这一流程也有可以优化的点。以redux为例,在从单一状态树中选择并分发状态数据时,可以考虑利用reselect创造选择器函数缓存状态获取,避免重复调用mapStateToProps函数。

当reducer的计算需求变大时,也可以考虑引用webworker中间件将这部分计算需求分发到worker线程,减轻渲染压力。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「性能优化」「Webpack」「React」「工程化」 标签。