React是一个UI层面的库,它采用虚拟DOM技术减少Javascript与真正DOM的交互,提升了前端性能;采用单向数据流机制,父组件通过props将数据传递给子组件,这样让数据流向一目了然。一旦组件的props或则state发生改变,组件及其子组件都将重新re-render和vdom-diff,从而完成数据的流向交互。但是这种机制在某些情况下比如说数据量较大的情况下可能会存在一些性能问题。下面就来分析react的性能瓶颈,并用结合着react-addons-perf工具来说明react组件拆分的重要性。
react性能瓶颈要了解react的性能瓶颈,就需要知道react的渲染流程。它的渲染可以分为两个阶段:
初始组件化
该阶段会执行组件及其所有子组件的render方法,从而生成第一版的虚拟dom。
组件更新渲染。
组件的props或者state任意发生改变就会触发组件的更新渲染。默认情况下其也会执行该组件及其所有子组件的render方法获取新的虚拟dom。
我们说的性能瓶颈指的是组件更新阶段的情况。
react组件更新流程通过上面分析可以知道组件更新具体过程如下:
执行该组件及其所有子组件的render方法获取更新后的虚拟DOM,即re-render,即使子组件无需更新。
然后对新旧两份虚拟DOM进行diff来进行组件的更新
在这个过程中,可以通过组件的shouldComponentUpdate方法返回值来决定是否需要re-render。
react的整个更新渲染流程可以借用一张图来加以说明:
默认地,组件的shouldComponentUpdate返回true,即React默认会调用所有组件的render方法来生成新的虚拟DOM, 然后跟旧的虚拟DOM比较来决定组件最终是否需要更新。
react性能瓶颈借图说话,例如下图是一个组件结构tree,当我们要更新某个子组件的时候,如下图的绿色组件(从根组件传递下来应用在绿色组件上的数据发生改变):
理想情况下,我们只希望关键路径上的组件进行更新,如下图:
但是,实际效果却是每个组件都完成re-render和virtual-DOM diff过程,虽然组件没有变更,这明显是一种浪费。如下图黄色部分表示浪费的re-render和virtual-DOM diff。
根据上面的分析,react的性能瓶颈主要表现在:
对于props和state没有变化的组件,react也要重新生成虚拟DOM及虚拟DOM的diff。
用shouldComponentUpdate来进行性能优化针对react的性能瓶颈,我们可以通过react提供的shouldComponentUpdate方法来做点优化的事,可以有选择的进行组件更新,从而提升react的性能,具体如下:
shouldComponentUpdate需要判断当前属性和状态是否和上一次的相同,如果相同则不需要执行后续生成虚拟DOM及其diff的过程,否则需要更新。
具体可以这么显示实现:
(nextProps(nextState, this.state) }
其中,isEqual方法为判断两个对象是否相等(指的是其对象内容相等,而不是全等)。
通过显示覆盖shouldComponentUpdate方法来判断组件是否需要更新从而避免无用的更新,但是若为每个组件添加该方法会显得繁琐,好在react提供了官方的解决方案,具体做法:
方案对组件的shouldComponentUpdate进行了封装处理,实现对组件的当前属性和状态与上一次的进行浅对比,从而决定组件是否需要更新。
react在发展的不同阶段提供两套官方方案:
一种是基于ES5的React.createClass创建的组件,配合该形式下的mixins方式来组合PureRenderMixin提供的shouldComponentUpdate方法。当然用ES6创建的组件也能使用该方案。
Example (props) ..shouldComponentUpdate.bind(this); }
该方案是在React 15.3.0版本发布的针对ES6而增加的一个组件基类:React.PureComponent。这明显对ES6方式创建的组件更加友好。
Example
需要指出的是,不管是PureRenderMin还是PureComponent,他们内部的shouldComponentUpdate方法都是浅比较(shallowCompare)props和state对象的,即只比较对象的第一层的属性及其值是不是相同。例如下面state对象变更为如下值:
state
因为state的value被赋予另一个对象,使nextState.value与this.props.value始终不等,导致浅比较通过不了。在实际项目中,这种嵌套的对象结果是很常见的,如果使用PureRenderMin或者PureComponent方式时起不到应有的效果。
虽然可以通过深比较方式来判断,但是深比较类似于深拷贝,递归操作,性能开销比较大。
为此,可以对组件尽可能的拆分,使组件的props和state对象数据达到扁平化,结合着使用PureRenderMin或者PureComponent来判断组件是否更新,可以更好地提升react的性能,不需要开发人员过多关心。
组件拆分组件拆分,在react中就是将组件尽可能的细分,便于复用和优化。拆分的具体原则:
这不太好理解,举个例子吧:假设我们定义一个父组件,其包含了5000个子组件。有一个输入框输入操作,每次输入一个数字,对应的那个子组件背景色变红。
..}
本例中,输入框组件和列表子组件有着明显的不同,一个是动态的,输入值比较频繁;一个是相对静态的,不管input怎么输入它就是5000项。输入框每输入一个数字都会导致所有组件re-render,这样就会造成列表子组件不必要的更新。
可以看出,上面列表组件的更新不容易被取消,因为输入组件和列表子组件的状态都置于父组件state中,二者共享;react不可能用shouldComponentUpdate的返回值来使组件一部分组件更新,另一部分不更新。 只有把他们拆分为不同的组件,每个组件只关心对应的props。拆分的列表组件只关心自己那部分属性,其他组件导致父组件的更新在列表组件中可以通过判断自己关心的属性值情况来决定是否更新,这样才能更好地进行组件优化。
这主要是从组件优化的角度考虑的,如果组件不需过多关注性能,可以忽略。
拆分组件之所以扁平化,是因为React提供的优化方案PureRenderMin或者PureComponent是浅比较组件的props和state来决定是否更新组件。