、react
react是用于构建用户界面的JS框架。所以react只负责解决view层的渲染。算法
virtual dom 其实是对实际Dom的一个抽象,是一个js对象。react全部的表层操做其实是在操做virtual dom。
通过diff算法会计算出virtual dom的差别,而后将这些差别进行实际的dom操做更新页面。性能优化
setState是“异步”的,调用setState只会提交一次state修改到队列中,不会直接修改this.state。框架
等到知足必定条件时,react会合并队列中的全部修改,触发一次update流程,更新this.state。dom
所以setState机制减小了update流程的触发次数,从而提升了性能。异步
因为setState会触发update过程,所以在update过程当中必经的生命周期中调用setState会存在循环调用的风险。函数
另外用于监听state更新完成,可使用setState方法的第二个参数,回调函数。在这个回调中读取this.state就是已经批量更新后的结果。性能
在实际开发中,setState的表现有时会不一样于理想状况。主要是如下两种。大数据
在mount流程中调用setState。
在setTimeout/Promise回调中调用setState。
在第一种状况下,不会进入update流程,队列在mount时合并修改并render。优化
在第二种状况下,setState将不会进行队列的批更新,而是直接触发一次update流程。
这是因为setState的两种更新机制致使的,只有在批量更新模式中,才会是“异步”的。
diff算法用于计算出两个virtual dom的差别,是react中开销最大的地方。
传统diff算法经过循环递归对比差别,算法复杂度为O(n3)。
react diff算法制定了三条策略,将算法复杂度从 O(n3)下降到O(n)。
ID
来区分。针对这三个策略,react diff实施的具体策略是:
另外,在对比同一层级的子节点时:
diff算法会以新树的第一个子节点做为起点遍历新树,寻找旧树中与之相同的节点。
若是节点存在,则移动位置。若是不存在,则新建一个节点。
在这过程当中,维护了一个字段lastIndex,这个字段表示已遍历的全部新树子节点在旧树中最大的index。
在移动操做时,只有旧index小于lastIndex的才会移动。
这个顺序优化方案其实是基于一个假设,大部分的列表操做应该是保证列表基本有序的。
能够推倒倒序的状况下,子节点列表diff的算法复杂度为O(n2)
因为react中性能主要耗费在于update阶段的diff算法,所以性能优化也主要针对diff算法。
减小diff算法触发次数实际上就是减小update流程的次数。
正常进入update流程有三种方式:
setState机制在正常运行时,因为批更新策略,已经下降了update过程的触发次数。
所以,setState优化主要在于非批更新阶段中(timeout/Promise回调),减小setState的触发次数。
常见的业务场景即处理接口回调时,不管数据处理多么复杂,保证最后只调用一次setState。
父组件的render必然会触发子组件进入update阶段(不管props是否更新)。此时最经常使用的优化方案即为shouldComponentUpdate方法。
最多见的方式为进行this.props和this.state的浅比较来判断组件是否须要更新。或者直接使用PureComponent,原理一致。
须要注意的是,父组件的render函数若是写的不规范,将会致使上述的策略失效。
// Bad case
// 每次父组件触发render 将致使传入的handleClick参数都是一个全新的匿名函数引用。
// 若是this.list 一直都是undefined,每次传入的默认值[]都是一个全新的Array。
// hitSlop的属性值每次render都会生成一个新对象
class Father extends Component {
onClick() {}
render() {
return <Child handleClick={() => this.onClick()} list={this.list || []} hitSlop={{ top: 10, left: 10}}/>
}
}
// Good case
// 在构造函数中绑定函数,给变量赋值
// render中用到的常量提取成模块变量或静态成员
const hitSlop = {top: 10, left: 10};
class Father extends Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.list = [];
}
onClick() {}
render() {
return <Child handleClick={this.onClick} list={this.list} hitSlop={hitSlop} />
}
}
复制代码
其中forceUpdate方法调用后将会直接进入componentWillUpdate阶段,没法拦截,所以在实际项目中应该弃用。
使用shouldComponentUpdate钩子,根据具体的业务状态,减小没必要要的props变化致使的渲染。如一个不用于渲染的props致使的update。
另外, 也要尽可能避免在shouldComponentUpdate 中作一些比较复杂的操做, 好比超大数据的pick操做等。
不须要渲染的props,合理使用context机制,或公共模块(好比一个单例服务)变量来替换。