虽然说使用 React 已经可以得到很好的性能了,但是还是会有一些使用方法的不对会导致 React 应用的性能下降。官方文档也有一篇文章专门介绍如何优化性能,「Avoid Reconciliation」这一节我觉得值得一看。
下面我要说的也是围绕「Avoid Reconciliation」这一节的内容来进行展开。首先「Reconciliation」是什么意思呢,我理解的是需要调用组件的 render()
方法进行重新渲染,用生成新的虚拟 DOM 与老的虚拟 DOM 对比,以此来决定是否需要更新 DOM。React 在重新渲染之前会去判断 shouldComponentUpdate()
函数返回的值。如果为 true
就是需要重新渲染,如果为 false
就什么也不做。shouldComponentUpdate()
默认返回 true
,也就是始终需要重新渲染。所以我们需要重新实现 shouldComponentUpdate()
只有在我们需要重新渲染的时候才返回 true
。这样一来是不是每个组件都需要我们去实现 shouldComponentUpdate()
,实际上是不用。如果你是通过继承 PureComponent
来实现的组件,只有在 state
或者 props
改变的时候才会需要重新渲染,可以看下 ReactCompositeComponent.updateComponent()
源码。建议多使用 PureComponent
|
|
我们可以想象一下在一个表单页面。有很多的 input
组件。每输入一个值都会导致所有的 input
重新渲染。为了优化,我们需要封装一下 input
组件。在只有自身改变的时候才重新渲染。如果页面上有10个 input
,那么优化过后就只用花之前的 1/10 的性能就可以完成之前的渲染任务。这样就指导我们一个组件不能写的太大,做好组件间的隔离,频繁改动的组件要不影响其他组件。
之前自己一直有个错误的想法,认为 PureComponent
实现的 shouldComponentUpdate()
方法里面 shallowEqual(prevProps, nextProps)
不能正确的判断 function
导致 props
里面如果传了 function
的话,即使所有属性都没改变,也会引起组件的重新渲染。当时还把这个当成教条牢记在心中,props
里面有 function
的话不能使用 PureComponent
,因为这会导致 shouldComponentUpdate()
不能像预期一样正常工作。直到写这篇文章的时候再去看以前的代码,才发现,原来我传入的 function
是一个柯里化函数,每次父组件 render()
都会生成一个新的函数。好囧。
为了避免我们无意识的在 render()
里面创建了一个新对象。就要避免对产生新对象的方法的使用。尤其是需要传递给子组件的 props
,这里有一个比 render()
是纯函数还要高的要求。不只要求同样的传入始终返回一样的结果,还要求如果输入没有变化的话,那么就不应该输入新的对象。换句话说不产生新对象就对了。包括自身的 props
和 state
。如果你是使用的 Redux 那么就应该在 reducer
里面就把所有数据准备好。
说了这么多,搞得我们像走钢丝样都不敢轻易写代码了。有没有比较好的方式去检查不应该重新渲染的地方却重新渲染了呢?既然我们能想到这个问题当然那些大牛也能想到,并且早就提供了成熟的类库供我们使用。这个类库就 是 why-did-you-update
。一旦我们在项目中引入了 why-did-you-update
,就会在控制台中以警告的形式提示我们哪些地方该优化了。
why-did-you-update
触发提示的条件是,如果 shouldComponentUpdate()
返回的是 true
, 那么组件的生命周期方法 componentDidUpdate()
调用时, why-did-you-update
就会去比较每个 props
的属性,每个 state
的属性,去判断到底是不是真的发生了变化。如果你是骗了 React 去更新页面,那么就不会逃脱 why-did-you-update
的法眼,不管是浅拷贝还是深拷贝。但是如果触发页面重新请求数据,但是数据还是以前的数据,没有更新,那么最好有一种缓存机制直接返回之前的数据。但是也不要忘了,一旦有数据变化一定要返回新的对象。