了解react
如何提升性能将有助于我们更好的编写代码。个人认为react
中很多的性能优化,其实都是围绕着react
的核心diff
算法来展开的,通过优化,减少diff
算法中一些不必要的步骤,从而来提高性能。下面是我平时开发总结出来的一些经验。
Component与PureComponent
一般来讲,很多人都是使用的Component,但是会带来一个问题,那就是在父组件的props
或者state
发生改变的时候,或者说的再准确一点,每当我们调用setState
或者props
发生改变的时候,都会再执行一次render
,会重新生成virtualdom
,这个时候,比如说在该父组件中还存在Home
和Area
两个组件,也会重新进行渲染(执行render -> 生成virtualDom -> 对比新旧virtualDom生成差异对象 -> 将生成的差异对象应用到真实dom上
),但是如果Home
和Area
没有任何改变那么差异对象不包含任何东西,那么就相当于重新渲染的时候前三步耗费了性能。
这种性能耗费其实是没必要的, 于是react
提供了两种方法给我们来优化这个过程。
Component中shouldComponentUpdate方法
该方法在Component
中没有继承实现,需要我们手动实现。并且在初次渲染和调用forceUpdate
的时候不触发
- shouldComponentUpdate(nextProps, nextState)
如果我们要判断该组件是否更新必须通过比较this.props
和nextProps
加上this.state
与nextState
, 判断他们是否改变,因为默认是返回true
的。如果改变了可以通过返回true
来告诉react
,需要进行前面说到的重新渲染的过程,否者返回false
。那么shouldComponentUpdate
后面的生命周期render() -> getSnapshotBeforeUpdate() -> componentDidUpdate()
都不会执行。这样就达到了提高性能的作用。
但是需要注意的一点是,虽然该组件没有被re-rendering
,但是如果该组件中还包含子组件,如果此时子组件的state
(这里我没有说还有props
)发生了改变,此子组件还是会被重新渲染,并不是说父组件的shouldComponentUpdate
返回false
,所有的子组件就不会重新渲染了。你可能现在会有点迷糊,不是说返回false
就不会重新渲染也不会执行render
的吗? 那怎么会重新渲染子组件呢? 让我们来想一想:如果通过比较发现该组件的state
和props
都没有发生改变,说明传给子组件的props
肯定没有变,此时唯一可能改变的就是子组件自生的state
。在开发中可能会出现这种情况,即父组件状态没有改变,但是子组件自身的state
发生改变。 而且在实际项目开发中,结合使用redux
来管理数据的时候,如果父组件的状态没有发生改变,子组件其实可能还是会受到props
的影响的。
还有一点是在比较的过程中,如果过于复杂也会导致过多的性能损耗,还不如不进行比较,进行默认的步骤。例如深度遍历、JSON.stringify
这种耗费大量的性能的操作。通常比较推荐的做法是进行浅比较,并且下面要介绍的PureComponent
也是使用的浅比较,什么是浅比较在下面PureComponent
专题中说。
使用PureComponent
PureComponent
内部帮我们实现了shouldComponentUpdate
,其他和Component
一样。但是在shouldComponentUpdate
进行的是一个浅比较,看看官方文档是怎么说的。
他这里并没有仔细说浅比交到底是怎么比较。经过我的调试,我发现浅比较具有以下特点。
- 对于
state
只比较第一层,并且如果是基本类型,那么比较他们的值,如果是引用值,那么比较他们的引用。 - 对于传递来的
props
, 也只比较第一层,规则和上面一样。
下面给几个例子:假设state
中的数据结构是这样
constructor (props) {
super(props)
this.state = {
count: 0,
person: {
// name: 'longjincen'
person: {
person: {
name: 'longjincen'
}
}
}
}
}
当我们继承的是Component
的时候,只要调用setState
就会触发重新渲染,但是当我们继承的是PureComponent
的时候,就不一样了。当我们像下面这样改变state的时候
handleClick = () => {
const { person } = this.state
person.person.person = {
name: 'xiaoya'
}
this.setState({
person: person
})
}
这个时候最外层的person
始终都是同一个引用,所以这个时候是不会触发重新渲染的,就算我们内部的person
是一个新的引用,因为它只比较第一层。所以当我们想要更新的时候,第一层返回的必须是一个新的对象,才会触发重新渲染。所以官方文档中也说了,如果数据结构比较复杂,那么可能会导致一些问题,要么当你知道改变的时候调用forceUpdate
,要么使用immutable
来包装你的state
,immutable
是一个js
库,用法和js
差不多,是facebook开发出来的,一般和PureComponent
搭配使用。
所以当我们使用PureComponent
的时候返回的state必须是一个新的对象,下面给出几种解决办法:
- 使用Object.assgin({}, xxx), 来创建一个新的对象返回
- 使用扩展运算符{ ...xxx }
1.
handleClick = () => {
const { person } = this.state
person.person.person.name = 'xiaoya'
this.setState({
person: Object.assign({}, person)
})
}
2.
handleClick = () => {
const { person } = this.state
person.person.person.name = 'xiaoya'
this.setState({
person: { ...person }
})
}
而对于接收到的前后的props
的比较和上面介绍的state
比较一样,但是需要注意的一种情况是,如果我们将上面的第三层的person
对象传给子组件,那么在子组件中,由于接收到的person
对象前后引用发生了改变,所以尽管父组件不会触发重新渲染,子组件还是会触发,而如果我们传递的是第二层或者第一层的person
由于引用没有改变,子组件也不会触发重新渲染。
最后要说的是在实际项目开发中往往是使用PureComponent
与immutable
搭配的方式
其他
无状态组件
无状态组件就是使用定义函数的方式来定义组件,这种组件相比于使用类的方式来定义的组件(有状态组件),少了很多初始化过程,更加精简,所以要是可以使用无状态组件应当尽可能的使用无状态组件,会大幅度提升效率
不要在render
中生成新的引用
定义函数、使用内联样式或者动态生成一些不依赖state
或props
的jsx
这些,凡是不依赖state
或props
的都应该提到render
之外,否则会造成每次render
的时候生成新的引用,会导致在diff
算法对比属性或节点的过程中发现两个引用不一致,对于react
来说,这说明属性值发生了改变,最后会被替换成新的引用,造成性能浪费。例如函数应该放在render
函数外定义,样式应该单独建一个文件,然后引入,使用生命周期函数或者自定义函数动态生成一些不依赖state
或props
的jsx
。
始终由用户触发改变state
在render
函数中,只能通过特定的用户事件来触发state
改变,否者容易造成不断刷新的死循环,像下面这样就是错误的
render () {
this.setState({
name: 'xcacsa'
})
}
始终使用key值
避免使用数组下标作为key
值, 应该确保唯一。在diff
算法中,key
值用来保证当列表中节点顺序改变的时候节点的复用,而不是全部替换。