首页 > 代码库 > React 组件性能优化探索实践

React 组件性能优化探索实践

转自:http://www.tuicool.com/articles/Ar6Zruq

React本身就非常关注性能,其提供的虚拟DOM搭配上Diff算法,实现对DOM操作最小粒度的改变也是非常的高效。然而其组件渲染机制,也决定了在对组件进行更新时还可以进行更细致的优化。

react组件渲染

react的组件渲染分为初始化渲染和更新渲染。

在初始化渲染的时候会调用根组件下的所有组件的render方法进行渲染,如下图(绿色表示已渲染,这一层是没有问题的):

技术分享

但是当我们要更新某个子组件的时候,如下图的绿色组件(从根组件传递下来应用在绿色组件上的数据发生改变):

技术分享

我们的理想状态是只调用关键路径上组件的render,如下图:

技术分享

但是react的默认做法是调用所有组件的render,再对生成的虚拟DOM进行对比,如不变则不进行更新。这样的render和虚拟DOM的对比明显是在浪费,如下图(黄色表示浪费的render和虚拟DOM对比)

技术分享

那么如何避免发生这个浪费问题呢,这就要牵出我们的 shouldComponentUpdate

shouldComponentUpdate

react在每个组件生命周期更新的时候都会调用一个 shouldComponentUpdate(nextProps, nextState) 函数。它的职责就是返回true或false,true表示需要更新,false表示不需要,默认返回为true,即便你没有显示地定义 shouldComponentUpdate 函数。这就不难解释上面发生的资源浪费了。

为了进一步说明问题,我们再引用一张官网的图来解释,如下图( SCU表示shouldComponentUpdate,绿色表示返回true(需要更新),红色表示返回false(不需要更新);vDOMEq表示虚拟DOM比对,绿色表示一致(不需要更新),红色表示发生改变(需要更新)):

技术分享

根据渲染流程,首先会判断shouldComponentUpdate(SCU)是否需要更新。如果需要更新,则调用组件的render生成新的虚拟DOM,然后再与旧的虚拟DOM对比(vDOMEq),如果对比一致就不更新,如果对比不同,则根据最小粒度改变去更新DOM;如果SCU不需要更新,则直接保持不变,同时其子元素也保持不变。

C1根节点,绿色SCU (true),表示需要更新,然后vDOMEq红色,表示虚拟DOM不一致,需要更新。

C2节点,红色SCU (false),表示不需要更新,所以C4,C5均不再进行检查

C3节点同C1,需要更新

C6节点,绿色SCU (true),表示需要更新,然后vDOMEq红色,表示虚拟DOM不一致,更新DOM。

C7节点同C2

C8节点,绿色SCU (true),表示需要更新,然后vDOMEq绿色,表示虚拟DOM一致,不更新DOM。

为了避免一定程度的浪费,react官方还在0.14版本中加入了无状态组件,如下:

// es5
function HelloMessage(props) {  
   return <div>Hello {props.name}</div>;
}

  

// es6
const HelloMessage = (props) => <div>Hello {props.name}</div>;

  

具体可参考官网: Reusable Components

既然明白了这关键所在,现在是时候向我们的大大小小一箩筐组件开刀了。

牛刀小试,直接把一些不需要更新的组件返回false

下面我们以音量图标为例,这是一个svg图标,不需要更新,所以直接return false

import React, {Component} from ‘react‘;
class Mic extends Component {
    constructor(props) {      
        super(props);
    }
    shouldComponentUpdate() {        
          return false;
    }
    render() {        
return (           
 <svg className="icon-svg icon-mic" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" aria-labelledby="title">
                <title>mic</title>
                <path className="path1" d="M15 22c2.761 0 5-2.239 5-5v-12c0-2.761-2.239-5-5-5s-5 2.239-5 5v12c0 2.761 2.239 5 5 5zM22 14v3c0 3.866-3.134 7-7 7s-7-3.134-7-7v-3h-2v3c0 4.632 3.5 8.447 8 8.944v4.056h-4v2h10v-2h-4v-4.056c4.5-0.497 8-4.312 8-8.944v-3h-2z"></path>
            </svg>
        )
    }
}

export default Mic;    

  

登堂入室,对数据进行对比确定是否需要更新

先来个官网的例子,通过判断id是否改变来确定是否需要更新:

shouldComponentUpdate: function(nextProps, nextState) {  
    return nextProps.id !== this.props.id;
}

  看起来也没那么玄乎,直接一个 !== 对比下就ok了,那是不是所有的都可以这样直接对比就可以呢? 我们先来看下js的两个数据类型(原始类型与引用类型)的各自比较

// 原始类型var a = ‘hello the‘;var b = a;
b = b + ‘world‘;console.log(a === b);  // false// 引用类型var c = [‘hello‘, ‘the‘];var d = c;
d.push(‘world‘);console.log(c === d); // true

  

我们可以看到a和b不等,但是c和d是一样一样的,这修改了d,也直接修改了c,那还怎么对比(关于原始类型与引用类型的区别这里就不说明了)。

现在看来我们得分情况处理了,原始类型数据和引用类型数据得采用不同的办法处理。

原始类型数据

这没什么好说的,直接比对就是了。但是每个人都是想偷懒的,这要是每个组件都要这样去写下也挺麻烦的,于是react官方有了插件帮我们搞定这事:

PureRenderMixin (es5的插件)

var PureRenderMixin = require(‘react-addons-pure-render-mixin‘);
React.createClass({
  mixins: [PureRenderMixin],

  render: function() {    
       return <div className={this.props.className}>foo</div>;
  }
});

  Shallow Compare (es6的插件)

var shallowCompare = require(‘react-addons-shallow-compare‘);
export class SampleComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {    
       return shallowCompare(this, nextProps, nextState);
  }

  render() {   
        return <div className={this.props.className}>foo</div>;
  }
} 

  

引用类型数据

既然引用类型数据一直返回true,那就得想办法处理,能不能把前后的数据变成不一样的引用呢,那样不就不相等了吗?于是就有了我们的不可变数据。

react官方提供了一个 Immutability Helpers

 

const newValue = http://www.mamicode.com/{"hljs-regexp">// 在这里做你想要的修改};// 快速检查 —— 只要检查引用newValue =http://www.mamicode.com/== oldValue; // false// 如果你愿意也可以用 Object.assign 语法const newValue2 = Object.assign({}, oldValue);

newValue2 === oldValue; // false

然后在 shouldComponentUpdate 中进行比对

shouldComponentUpdate(nextProps) {    

  return isObjectEqual(this.props, nextProps); }

我们目前采用的是在reducer里面更新数据使用 Object.assign({}, state, {newkey: newValue} (数据管理采用redux),然后在组件里面根据某个具体的字段判断是否更新,如title或id等,而不是判断整个对象:
shouldComponentUpdate: function(nextProps, nextState){    
     return nextProps.title !== this.props.title;
}

  

 

React 组件性能优化探索实践