![b9f198ffb1342a43d7d767253bb3c0ff.png](https://i-blog.csdnimg.cn/blog_migrate/2c42cc958e4c5906d951afe478bbc06a.jpeg)
探究React 性能优化
React为设计高性能的React应用程序提供了很多优化,可以通过遵循一些最佳实践来实现。性能优化的关键在于是否能够减少不必要的Render,触发Render主要有下面的情况:
- 发生setState。
- props的改变。
- 使用
forceUpdate
。
下面给出了一些常见的优化方案,我们将解读、实践它们,对于部分内容我们会深入源码分析其原理。
React.PureComponent
组件嵌套造成的额外渲染
案例
来看下面这个组件嵌套的代码:
import React from "react";
class Footer extends React.Component {
render() {
console.log("Footer component render!");
return (
<div>Footer组件</div>
)
}
}
const List = () => {
console.log("List component render!");
return (
<ul>
<li>Hello</li>
<li>world</li>
</ul>
)
}
class Main extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
add() {
console.log("add按钮被单击!");
this.setState({
count: this.state.count + 1
})
}
render() {
console.log("Main render!");
return (
<div>
<div>current:{this.state.count}</div>
<button onClick={() => this.add()}>add one</button>
<List/>
<Footer/>
</div>
)
}
}
export default Main;
运行之后,页面如图所示:
![08f0ed26fa223b696e286c888a065f88.png](https://i-blog.csdnimg.cn/blog_migrate/4076748dfa03a970d1b1107bdc784f61.jpeg)
页面初次渲染,打印了如上图的内容,这很正常 -- 每个组件都得被render一次。
但当我们点击Main组件中的add按钮时(如下图),三个组件被重新render了!但是Footer组件、list组件的render是毫无必要的。
![bef1fe671660c97fa4c207804fc7fa05.png](https://i-blog.csdnimg.cn/blog_migrate/0817b884740869d4739ab338832826fc.jpeg)
使用PureComponent
设想一下,假如我们能够在List和Footer组件被渲染之前对比一下前后的props
是否改变 、state
是否改变,再决定是否渲染不就可以了吗?我们可以使用shouldComponentUpdate
这个生命周期函数来实现,它返回一个布尔值,来定义是否render,下面是官方文档的截图:
![2cf5b9093cbf4a71de60a2cc5c7c4572.png](https://i-blog.csdnimg.cn/blog_migrate/54fc814e8e44b1bbee490006cdfbe870.jpeg)
但是如果我们每个文件都写一遍,那么实在太麻烦了,所以我们可以使用PureComponent,下面我们尝试修改上面的Footer组件。
class Footer extends React.PureComponent {
render() {
console.log("Footer component render!");
return (
<div>Footer组件</div>
)
}
}
从下图中可以看出,Footer组件没有被重新渲染,美中不足的是,List组件(它是一个函数式组件)仍然发生了渲染,我们下面会解决它。
![b2836906ff3edb8a183715e688bf1b45.png](https://i-blog.csdnimg.cn/blog_migrate/69a78b6a82992014a3fb51ab327085c3.jpeg)
PureComponent原理
根据上面的描述,我们可以猜出PureComponent
的原理无非就是比较前后props、state是否改变,我们先看看PureComponent
:
![c2b4f0e3b1648f4f04d5c1105eb3172a.png](https://i-blog.csdnimg.cn/blog_migrate/2c09b86f89d69e15bd436cb43a2ce501.jpeg)
注意最后设置isPureReactComponent
为true,React通过调用checkShouldComponentUpdate
来判断,这个函数位于packages/react-reconciler/src/ReactFiberClassComponent.js
下,注意下面的两个红框:
- 第一部分:判断开发者是否使用了
shouldComponentUpdate
,如果是,执行并返回结果。(ps.出现的startPhaseTimer
貌似是一个计时功能,我们这里不做探讨) - 第二部分:如果这个组件是PureComponent,执行第二个红框的代码,也是核心部分了 -- 它通过调用
shallowEqual
比较state和props来决定是否需要更新。
![e044486bf07f5f671b4225c1918c8d72.png](https://i-blog.csdnimg.cn/blog_migrate/ef99f60d0bb34f9d06bc31cb47868c54.jpeg)
来看看shallowEqual
,它位于packages/shared/shallowEqual.js
,下面以注释的形式给出解析:
function shallowEqual(objA: mixed, objB: mixed): boolean {
// 面向基本数据类型的比较,下面会单独提
if (is(objA, objB)) {
return true;
}
// object 和 null 的情况,也返回false
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
// 拿出所有的keys
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
// 先比较长度,长度不等直接返回false
if (keysA.length !== keysB.length) {
return false;
}
// 循环遍历比较
for (let i = 0; i < keysA.length; i++) {
if (
// 判断为true的条件:
/