React 移动 web 极致优化

原文地址:https://github.com/lcxfs1991/blog/issues/8

最近一个季度,我们都在为手Q家校群做重构优化,将原有那套问题不断的框架换掉。经过一些斟酌,决定使用react 进行重构。选择react,其实也主要是因为它具有下面的三大特性。

React的特性

1. Learn once, write anywhere

学习React的好处就是,学了一遍之后,能够写web, node直出,以及native,能够适应各种纷繁复杂的业务。需要轻量快捷的,直接可以用Reactjs;需要提升首屏时间的,可以结合React Server Render;需要更好的性能的,可以上React Native。

但是,这其实暗示学习的曲线非常陡峭。单单是Webpack+ React + Redux就已够一个入门者够呛,更何况还要兼顾直出和手机客户端。不是一般人能hold住所有端。

2. Virtual Dom

Virtual Dom(下称vd)算是React的一个重大的特色,因为Facebook宣称由于vd的帮助,React能够达到很好的性能。是的,Facebook说的没错,但只说了一半,它说漏的一半是:“除非你能正确的采用一系列优化手段”。

3. 组件化

另一个被大家所推崇的React优势在于,它能令到你的代码组织更清晰,维护起来更容易。我们在写的时候也有同感,但那是直到我们踩了一些坑,并且渐渐熟悉React+ Redux所推崇的那套代码组织规范之后。

那么?

上面的描述不免有些先扬后抑的感觉,那是因为往往作为React的刚入门者,都会像我们初入的时候一样,对React满怀希望,指意它帮我们做好一切,但随着了解的深入,发现需要做一些额外的事情来达到我们的期待。

对React的期待

初学者对React可能满怀期待,觉得React可能完爆其它一切框架,甚至不切实际地认为React可能连原生的渲染都能完爆——对框架的狂热确实会出现这样的不切实际的期待。让我们来看看React的官方是怎么说的。React官方文档在Advanced Performanec这一节,这样写道:

One of the first questions people ask when considering React for a project is whether their application will be as fast and responsive as an equivalent non-React version

显然React自己也其实只是想尽量达到跟非React版本相若的性能。React在减少重复渲染方面确实是有一套独特的处理办法,那就是vd,但显示在首次渲染的时候React绝无可能超越原生的速度,或者一定能将其它的框架比下去。因此,我们在做优化的时候,可的期待的东西有:

  1. 首屏时间可能会比较原生的慢一些,但可以尝试用React Server Render (又称Isomorphic)去提高效率

  2. 用户进行交互的时候,有可能会比原生的响应快一些,前提是你做了一些优化避免了浪费性能的重复渲染。

以手Q家校群功能页React重构优化为例

手Q家校群功能页主要由三个页面构成,分别是列表页、布置页和详情页。列表页已经重构完成并已发布,布置页已重构完毕准备提测,详情页正在重构。与此同时我们已完成对列表页的同构直出优化,并已正在做React Native优化的铺垫。

这三个页面的重构其实覆盖了不少页面的案例,所以还是蛮有代表性的,我们会将重构之中遇到的一些经验穿插在文章里论述。

在手Q家校群重构之前,其实我们已经做了一版PC家校群。当时将native的页面全部web化,直接就采用了React比较常用的全家桶套装:

  1. 构建工具 => gulp + webpack

  2. 开发效率提升 => redux-dev-tools + hot-reload

  3. 统一数据管理=> redux

  4. 性能提升 => immutable + purerender

  5. 路由控制器 => react-router(手Q暂时没采用)

为什么我们在优化的时候主要讲手Q呢?毕竟PC的性能在大部份情况下已经很好,在PC上一些存在的问题都被PC良好的性能掩盖下去。手机的性能不如PC,因此有更多有价值的东西深挖。开发的时候我就跟同事开玩笑说:“没做过手机web优化的都真不好意思说自己做过性能优化啊“。

构建针对React做的优化

我在《性能优化三部曲之一——构建篇》提出,“通过构建,我们可以达成开发效率的提升,以及对项目最基本的优化”。在进行React重构优化的过程中,构建对项目的优化作用必不可少。在本文暂时不赘述,我另外开辟了一篇《webpack使用优化(react篇)》进行具体论述。

开发效率提升工具

超级产品经理)。

请尽量使用const element

这个用法是工业聚在React讨论微信群里教会的,我们可以将不怎么变动,或者不需要传入状态的component写成const element的形式,这样能加快这个element的初始渲染速度。

路由控制与拆包

当项目变得更大规模与复杂的时候,我们需要设计成SPA,这时路由管理就非常重要了,这使特定url参数能够对应一个页面。

PC家校群整个设计是一个中型的SPA,当js bundle太大的时候,需要拆分成几个小的bundle,进行异步加载。这时可以用到webpack的异步加载打包功能,require。

在重构手Q家校群布置页的时候,我们有不少的浮层,列表有布置页内容主浮层、同步到多群浮层、科目管理浮层以及指定群成员浮层。这些完全可以使用react-router进行管理。但是由于当时一早使用了Immutablejs,js bundle已经比较大,我们就不打算使用react-router了。但后面仍然发现包比重构前要大一些,因此为了保证首屏时间不慢于重构前,我们希望在不用react-router的情况下进行分包,其实也并不难,如下面2幅图:

首先在切换浮层方法里面,使用require.ensure,指定要加载哪个包。
在setComponent方法里,将component存在state里面。
在父元素的渲染方法里,当state有值的时候,就会自动渲染加载回来的component。

性能数据

首屏可交互时间

目前只有列表页发布外网了,我们比较了优化前后的首屏可交互时间,分别有18%和5.3%的提升。

渲染FPS

更新于2016年7月2日

Android

React重构后第一版,当时还没做任何的优化,发现平均FPS只有22(虽然Android的肉眼感受不出来),而后面使用Immutable或者Lodash.merge都非常接近,能达到42或以上。而手机QQ可接受的FPS最少值是30FPS。因此使用Immutable和Lodash.merge的优化还是相当明显的。

  1. 重构后第一版

  2. Immutable

  3. Lodash.merge

iOS

在iOS上的fps差距尤为明显。重构后第一版,拉了大概5屏之后,肉眼会有卡顿的感觉,拉到了10屏之后,数据开始掉到了20多30。而Immutable和Lodash.merge则大部份时间保持在50fps以上,很多时候还能达到非常流畅的60fps。

  1. 重构后第一版

  2. Immutable

  3. Lodash.merge

Chrome模拟器

用Chrome模拟器也能看出一些端倪。在Scripting方面,Immutable和Lodash.merge的耗时是最少的,约700多ms,而重构后的第一版则需要1220ms。Lodash.merge在rendering和painting上则没占到优势,但Immutable则要比其它两个要少30% - 40%。由于测试的时候是在PC端,PC端的性能又极好,所以不管是肉眼,还是数据,对于不是很复杂的需求,总体的渲染性能看不出非常明显的差距。

  1. 重构后第一版

  2. Immutable

  3. Lodash.merge

从上面的数据看来,在移动端使用Immutable和Lodash.merge相对于不用,会有较大的性能优势,但Immutable相对于Lodash.merge在我们需求情景下暂时没看出明显的优势,笔者估计可能是由于项目数据规模不大,结构不复杂,因此Immutable的算法优势并没有充分发挥出来。

测试注明

Android端测试FPS是使用了腾讯开发的GT随身调。而iOS则使用了Macbook里xCode自带的instrument中的animation功能。Chrome模拟器则使用了Chrome的timeline。测试的方式是匀速滚动列表,拉出数据进行渲染。

React性能优化军规

我们在开发的过程中,将上面所论述的内容,总结成一个基本的军规,铭记于心,就可以保证React应用的性能不至于太差。

渲染相关

  1. 提升级项目性能,请使用immutable(props、state、store)

  2. 请pure-render-decorator与immutablejs搭配使用

  3. 请慎用setState,因其容易导致重新渲染

  4. 谨慎将component当作props传入

  5. 请将方法的bind一律置于constructor

  6. 请只传递component需要的props,避免其它props变化导致重新渲染(慎用spread attributes)

  7. 请在你希望发生重新渲染的dom上设置可被react识别的同级唯一key,否则react在某些情况可能不会重新渲染。

  8. 请尽量使用const element

tap事件

1. 简单的tap事件,请使用react-tap-event-plugin

开发环境时,最好引入webpack的环境变量(仅在开发环境中初始化),在container中初始化。生产环境的时候,请将plugin跟react打包到一起(需要打包在一起才能正常使用,因为plugin对react有好多依赖),外链引入。

目前参考了这个项目的打包方案:

  1. https://github.com/hartmamt/react-with-tap-events

  2. Facebook官方issue: https://github.com/facebook/react/blob/bef45b0b1a98ea9b472ba664d955a039cf2f8068/src/renderers/dom/client/eventPlugins/TapEventPlugin.js

  3. React-tap-event-plugin github: https://github.com/zilverline/react-tap-event-plugin

2. 复杂的tap事件,建议使用tap component

家校群列表页的每个作业的tap交互都比较复杂,出了普通的tap之外,还需要long tap和swipe。因此我们只好自己封装了一个tap component

Debug相关

  1. 移动端请慎用redux-devtools,易造成卡顿

  2. Webpack慎用devtools的inline-source-map模式
    使用此模式会内联一大段便于定位bug的字符串,查错时可以开启,不是查错时建议关闭,否则开发时加载的包会非常大。

其它

  1. 慎用太新的es6语法。
    Object.assign等较新的类库避免在移动端上使用,会报错。

Object.assign目前使用object-assign包。或者使用babel-plugin-transform-object-assign插件。会转换成一个extends的函数:

var _extends = ...;_extends(a, b);

如有错误,请斧正!

关键字:react.js, 前端, 性能, 性能优化


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部