写在前面的话:
要想解决问题,首先得找到问题的根源,所以,说起性能分析,还是要从其生命周期和渲染机制说起。
1.渲染机制
react的组件渲染分为初始化渲染和更新渲染,在初始化渲染的时候会调用根组件下的所有组件的render方法进行渲染。
但是当我们要更新某个子组件的时候,是从根组件传递下来应用在子组件上的数据发生改变。
我们只是希望调用关键路径上组件的render就好了。
但是,
react的默认做法是调用所有组件render,再对生成的虚拟DOM进行对比,如不变就不进行更新。这样的render和虚拟DOM的对比明显实在浪费时间。
注意:
拆分组件是有利于复用和组件优化的,其次,生成虚拟DOM并进行对比发生在render之后,而不是render之前。
总得来说,父亲组件的props和state发生变化时,他和他的子子孙孙组件等后代都要重新渲染。
2.更新阶段的生命周期
1.componementWillReceiveProp:当挂载的组件接受到新的props时会被调用,此方法应该被用于比较this.props和nextProps以用于使用this.setState()执行状态转换,(组件内部数据有变化,使用state,但是在更新阶段又要在props改变的时候改变state,则在这个生命周期里面)
2.shouldComponentUpdate:当组件决定任何改变是否要更新到DOM时被调用,作为一个优化实现比较this.props和nextProps,this.state和nextState,如果应该跳过更新,返回false。
3.componentWillUpdate:在更新发生前被立即调用,此时不能调用this.setState()
4.componentDidUpdate:在更新发生后被立即调用。(可以在DOM更新完之后,做一些收尾工作)
react的优化是基于shouldComponentUpdate的,该生命周期默认返回true,所以一旦prop或state有任何变化,都会引起重新render。
所以,可以得出react的性能优化就是围绕shouldComponentUpdate(SCU)方法来进行的,其优化无外乎两点:
- 缩短SCU方法的执行时间(或者直接不执行)
- 没必要的渲染,SCU就该返回false
shouldComponentUpdate:
react在每个组件生命周期更新的时候都会调用一个shouldComponentUpdate(nextprops,nextState)
函数,他的职责就是返回true,或者false,true表示需要更新,false就是表示不需要,默认值是返回true的,即便你没有显示的定义shouldComponentUpdate函数,他的返回值还是true。
根据渲染流程,首先会判断shouldcomponentUpdate是否需要更新,如果需要更新,则调用组件的render生成新的虚拟DOM,然后再与旧的虚拟DOM进行对比,如果对比一致就不更新,如果对比不同,则根据最小粒度改变去更新DOM,如果scu不需要更新,则直接保持不变,同时其子元素也保持不变。
注意:错误写法
- {…this.props}(不能滥用,只传递component需要的props即可,传递的太多,或者层次传的太深,都会加重shuoldComponentUpdate,其里面的数据比较负担,因此一定要慎重使用
spread attributes(<Component {...props}/>))
-
::this.handleChange()
(将该方法的bind一律置于constructor)
- 复杂的页面不要在一个组件中写完
- 尽量使用
const element
定义
- map里面添加key,并且key不要使用index(可变的)
- 尽量少的使用setTimeout或者不可控的refs,DOM操作
- props和state的数据尽可能简单明了,扁平化。
- 使用return null而不是css的
display:none
来控制节点的隐藏。保证同一时间页面的DOM节点尽可能的少。
3.性能分析工具
React特色工具:Perf
react官方提供一个插件react.addons.perf可以帮助我们分析组件的性能,以确认是否需要优化。
Perf 是react官方提供的性能分析工具。Perf最核心的方法莫过于Perf.printWasted(measurements)
,该方法会列出那些没必要的组件渲染。很大程度上,React的性能优化就是干掉这些无谓的渲染。
操作:
打开console面板,先输入Perf.start()执行一些组件操作,引起数据变动,组件更新,然后输入Perf.stop()。(建议一次只执行一个操作,好进行分析)
然后在输入Perf.printInclusive将所有涉及到的组件render打印下来。(官方图片)
或者输入Perf.printWasted()查看不需要的浪费组件的render。
优化前:
优化后:
4.其它检测工具
react-perf-tool为react提供了一种可视化的性能检测方案,其同样是基于React.addons,但是使用图标来显示结果更加方便。
1.PureRenderMixin(基于es5)
var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div className={this.props.className}>foo</div>;
}
});
2.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>;
}
}
pureRender很简单,就是把穿进来的component的shouldComponentUpdate给重写掉,原来的shouldComponentUpdate,无论怎样都是return true
,现在不会这样了,我要用shallowCompare比一比,shallowCompare代码及其简单,如下:
function shallowCompare(instance, nextProps, nextState) {
return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
}
但是这样做还是有缺点的:
shallowEqual其实只比较props的第一层,子属性是不是相同,如果props是如下:
{
detail: {
name: "123",
age: "123"
}
}
他只会比较props.detail===nextProps.detail
,这就会导致在传入复杂的数据的情况下,优化会失效。
7.补充
react在15.3.0里面加入了react.PureComponent一个可继承的新的基础类,用来替换react-addons-pure-render-mixin,用法如下:
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
8.immutable.js
我们也可以在shouldComponentUpdate()中使用deepCopy和deepCompare来避免无必要的render(),但是deepCopy和deepCompare一般都是非常耗性能的。
Immutable Date就是一旦创建,就不能再被更改的数据。对Immutable对象的任何修改或者添加删除操作都会返回一个新的Immutable对象。
Immutable实现的原理是Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变,同时为了避免deeepCopy把所有的节点都复制一遍带来的性能损耗,Immutable使用了,Structural Sharing(结构共享)即如果对象树中一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点则进行共享。
Immutable则提供了简洁高效的判断数据是否变化的方法,只需要===和is比较就能知道是否需要执行render(),而这个操作几乎0成本,所以可以极大提高性能,修改后的shouldComponentUpdate是这样的:
import { is } from 'immutable';
shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
const thisProps = this.props || {}, thisState = this.state || {};
if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
Object.keys(thisState).length !== Object.keys(nextState).length) {
return true;
}
for (const key in nextProps) {
if (!is(thisProps[key], nextProps[key])) {
return true;
}
}
for (const key in nextState) {
if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) {
return true;
}
}
return false;
}