美文网首页
高性能React开发

高性能React开发

作者: amnsss | 来源:发表于2017-01-14 22:28 被阅读228次
    450-500ms 延时

    项目开发到一大半效率都很高,很顺利。但是开发完图标详情页,当渲染上千个图标并展示时,出问题了。页面所有交互都变得卡顿。上图是一个点击弹出下载对话框的示例,大概有半秒延时。

    你不知道的Render

    其实我们碰到了一个React开发中最普遍的一个性能问题——重复渲染。先讲点基础的,关于React组件的更新。

    我们知道React渲染页面是调用组件的render方法进行渲染。我们更新组件是通过setState方法,触发组件重新render,更新页面。上图的例子,我们希望调用根组件的setState改变数据状态,使最终传到绿色组件的数据状态发生改变,使绿色组件得到更新。我们希望的组件调用render方法路径如下绿色组件

    实际上会是这样的吗?

    实际所有组件都进行了render,然后产生新的虚拟DOM和以前进行diff,然后根据不一样的地方进行统一的DOM操作,更新页面。所以图上黄色组件调用的render,都是重复渲染。那么如何避免这种浪费呢?

    shouldComponentUpdate

    很简单,利用React自身提供的生命周期函数

    • shouldComponentUpdate(nextProps, nextState)

    他会在组件render之前执行,它返回false则不再执行组件的render,true则执行render,默认返回true。所以默认是不会避免重复渲染的,需要我们自己实现避免逻辑。

    好我们来实现避免重复渲染的demo:

    我们在每个图标组件IconButton上实现了避免重复渲染的demo逻辑。看下效果:

    90-100ms 延时

    现在只有90-100ms 延时,速度提升了80%。但是我们想一想我们还有优化的空间吗?

    组件拆分

    进一步优化就体现出组件拆分的重要性了。我们刚才shouldComponentUpdate是在每个IconButton里实现的,可能有成百上千个IconButton,我们为了避免重复渲染它每次都要执行成百上千次shouldComponentUpdate里的demo,这是不是也有点浪费。我们为什么不判断数组有没改变,而不是一个一个判断数组里元素改没改变,来避免重复渲染。所以我们重新划分组件:

    如图所示我们把IconButton划分为IconButtons的子组件,在IconButtons上实现避免重复渲染逻辑,只需要判断传入的icons数组有没有变化。看下效果:

    20-30ms 延时

    immutable对象

    一路优化下来看似挺简单的,实际每次写shouldComponentUpdate里面的demo逻辑非常麻烦的。上面的示例都简化了的,如何组件添加了其他属性,还需要在shouldComponentUpdate添加监控其变化的代码逻辑。
    所以官方提供了pure-render-mixin这个包,封装了shouldComponentUpdate的代码逻辑,方便我们直接使用。但是它有一个缺陷,只对对象第一层属性的变化监控有效,因为它只是实现对nextpropsnextState的一个浅比较。因为实现深比较,深拷贝同样是非常耗性能的。
    所以当应用比较复杂时,最好使用React优化神器 immutable对象。不具体讲immutable对象API使用,只是讲React使用它的原理,搞清楚它的作用和威力才有使用的冲动嘛。

    immutable
    immutable对象即——不可变对象,每次修改对象的属性都会返回一个新的对象。但这个新的对象要打个引号,它其实是一种共享型的数据结构。如图所示,被修改的属性节点会一直回溯它的父节点,把它们都拷贝成新的值,但不受影响的其他分支内容还是共享以前的。这样有什么好处呢?
    当新的props对象传入组件的话,子组件只需要判断引用就可判断是否更新组件。用这种数据结构,我们要判断节点是否要更新,只需要比较传进来的props值或引用就行,比较成本几乎为0。
    使用上搭配Flux或Redux使用,在reducer层统一用immutable对象API管理state的修改与更新。Get到那么多优化技能,还有啥性能问题能难道我们?

    还会有问题?

    竟然提出了,肯定是还有问题要解决。

    提供稳定的key

    我们发现我的图标项目页面,删除图标时响应非常慢。我们看下了下这个页面的demo实现,用的是一个列表类组件——用数组map返回的一个组件数组。然后在官网上看到列表类组件的使用最好提供稳定的key。
    因为React是通过两种方式识别一个组件:

    1. 组件的类名
    2. 组件的key

    如果在渲染组件时发现新的虚拟DOM和之前不是相同的组件渲染的,则不用进行虚拟DOM的diff,直接使用新的虚拟DOM渲染页面。所以我们这个页面导致慢的原因是因为使用了index作为key
    删除前的icons数组

    icons = [
      { id: 1, name: 'xxx'}, // index 0
      { id: 2, name: 'xxx'}, // index 1
      { id: 3, name: 'xxx'}, // index 2
      { id: 4, name: 'xxx'}, // index 3
      ...
      { id: 1000, name: 'xxx'}, // index 999
    ]
    

    删除第二个图标后

    icons = [
      { id: 1, name: 'xxx'}, // index 0
      { id: 3, name: 'xxx'}, // index 1
      { id: 4, name: 'xxx'}, // index 2
      ...
      { id: 1000, name: 'xxx'}, // index 998
    ]
    

    使用index作为key,React在渲染组件时发现从第二个组件开始每个组件的虚拟DOM和之前比内容都发生了变化,需要进行更新操作,这一更新就是998个组件啊。
    如果使用id作为key,React在渲染组件后根据id去找旧的组件的虚拟DOM,由于每个组件的id前后没有发生变化,所以diff不会做大量的更新,React只会发现新的虚拟DOM少量一个id=2的组件,所以只是进行一个删除操作。
    同理,列表类组件如果存在排序,增删等操作提供稳定的key,可以减少很多DOM的更新操作。

    diff也会慢

    在图标库详情页面我们又遇到了一个问题,当用slider批量控制页面的图标大小时,非常卡顿。这次我们姿势都用对了,是什么造成如此卡顿的?


    滑动卡顿

    我们对整个渲染过程捋了一下,觉得所有图标进行diff更新size的时候,diff过程计算量其实是非常大的,会不会是diff过程导致慢的?我们尝试不经过React渲染,直接操作DOM,相当于跳过渲染虚拟DOM,diff过程,结果很流畅。


    滑动顺畅

    这里的解决办法不是很优雅,但是提供了一个思考性能问题的方向。有时候我们觉得diff过程是多余的,想直接渲染新的内容,这里还可以提供一种方案。在上面提到列表类组件是根据key来识别组件的,如果我们每次为所有组件提供新的key,是会直接跳过diff过程,相当于以第一次渲染页面。

    终于可以上线了

    项目开发完成,但是要把它放到生产环境我们还是有一些事情要做的。

    1. 压缩去掉注释
    new webpack.optimize.UglifyJsPlugin({
          output: {
              comments: false
          }
          compress: {
            warnings: false
          }
        }),
    
    1. 分离样式文件
    {
            test: /\.less$/,
            loader: ExtractTextPlugin.extract('style-loader/useable', 'css-loader?minimize!postcss-loader!less-loader?{"sourceMap":true,"modifyVars":' + JSON.stringify(theme) + '}')
    },
    
    1. 生产模式打包React
    new webpack.DefinePlugin({
          'process.env': {
            'NODE_ENV': JSON.stringify('production')
          },
          ...
    }),
    

    去掉开发模式下属性的验证和警告信息的记录。这样整体性能会得到提升,如果你之前还存在没有解决的性能问题,或许就消失啦。

    总结优化tips

    • shouldComponentUpdate避免重复渲染
    • redux + immutable应对大型复杂应用
    • 拆分组件尽量让组件更小
    • 性能分析工具react-addons-perf分析性能瓶颈
    • 列表类组件提供稳定的key
    • 方法bind一律置于constructor中,避免直接传对象,数组字面量属性
    • 慎用对象扩展运算符(...)
    • ...

    相关文章

      网友评论

          本文标题:高性能React开发

          本文链接:https://www.haomeiwen.com/subject/mhaqbttx.html