react性能优化

React是一个专注于UI层的框架,它使用虚拟DOM技术,以保证它UI的高速渲染;使用单向数据流,因此它数据绑定更加简单;
那么它内部是如何保持简单高效的UI渲染呢?这种渲染机制有可能存在什么性能问题呢?

React组件渲染问题引出

React不直接操作DOM,它在内存中维护一个快速响应的DOM描述,render方法返回一个DOM的描述,
React能够计算出两个DOM描述的差异,然后更新浏览器中的DOM。这就是著名的DOM Diff。

就是说React在接收到属性(props)或者状态(state)更新时,就会更新UI。所以React整个UI渲染是比较快的。但是这里面可能出现的问题是:

假设我们定义一个父组件,其包含了5000个子组件。我们有一个输入框输入操作,每次输入一个数字,对应的那个子组件背景色变红。

1
2
3
4
5
6
7
<Components>
<Components-1 />
<Components-2 />
<Components-3 />
...
<Components-5000 />
</Components>

这样我们输入数字1,则子组件1背景色变化,但是在这个过程中,所有的子组件都进行了重新渲染,导致整体渲染变慢。
造成这种现象的原因是 React中父组件更新默认触发所有子组件更新。

同时,我们经常在遍历列表元素时候会遇到这样的提示:

1
Warning: Each child in an array or iterator should have a unique "key" prop.

这就是我们今天要讨论的两个性能优化点:

1
2
父组件更新默认触发所有子组件更新
列表类型的组件默认更新方式非常复杂

React性能检测工具

安装 react 性能检测工具 npm i react-addons-perf –save,然后在./app/index.jsx中

1
2
3
4
5
// 性能测试
import Perf from 'react-addons-perf'
if (__DEV__) {
window.Perf = Perf
}

检测方法,在浏览器控制台输入如下命令:

1
2
3
开始记录:Perf.start()
结束记录:Perf.stop()
打印结果:printInclusive()

运行程序。在操作之前先运行Perf.start()开始检测,
然后进行若干操作,运行Perf.stop停止检测,
然后再运行Perf.printWasted()即可打印出浪费性能的组件列表。
在项目开发过程中,要经常使用检测工具来看看性能是否正常。

如果性能的影响不是很大,例如每次操作多浪费几毫秒、十几毫秒,个人以为没必要深究,但是如果浪费过多影响了用户体验,就必须去搞定它。

PureRenderMixin 优化

React 最基本的优化方式是使用PureRenderMixin,
安装工具 npm i react-addons-pure-render-mixin –save,然后在组件中引用并使用

1
npm i react-addons-pure-render-mixin --save
1
2
3
4
5
6
7
8
9
import React from 'react'
import PureRenderMixin from 'react-addons-pure-render-mixin'
class List extends React.Component {
constructor(props, context) {
super(props, context);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
//其原理就是重写了 shouldComponentUpdate 方法。
}

React 有一个生命周期 hook 叫做shouldComponentUpdate,
组件每次更新之前,都要过一遍这个函数,如果这个函数返回true则更新,如果返回false则不更新。
而默认情况下,这个函数会一直返回true,就是说,如果有一些无效的改动触发了这个函数,也会导致无效的更新
那么什么是无效的改动?之前说过,组件中的props和state一旦变化会导致组件重新更新并渲染,
但是如果props和state没有变化也莫名其妙的触发更新了呢(这种情况确实存在)———— 这不就导致了无效渲染吗?

1
2
3
4
这里使用
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
的意思是重写组件的shouldComponentUpdate函数,
在每次更新之前判断props和state,如果有变化则返回true,无变化则返回false

因此,我们在开发过程中,在每个 React 组件中都尽量使用PureRenderMixin

PureComponent

React 15.3.0 新增了一个 PureComponent 类,
以 ES2015 class 的方式方便地定义纯组件 (pure component),用于取代之前的 PureRenderMixin。

这个类的用法很简单,如果你有些组件是纯组件,那么把继承类从 Component 换成 PureComponent 即可。
当组件更新时,如果组件的 props 和 state 都没发生改变,render 方法就不会触发,
省去 Virtual DOM 的生成和比对过程,达到提升性能的目的。

1
2
3
4
5
6
import React, { PureComponent } from 'react'
class Example extends PureComponent {
render() {
// ...
}
}

虽然React提供了Virtual DOM DOM Diff 等优秀的能力来提高渲染性能,但是在实际使用过程中,我们经常会遇到父组件更新,
不需要更新所以子组件的场景(分页),此时必须考虑利用React本周的渲染机制来进行优化。

相关代码:https://github.com/younth/react-tutorial/tree/master/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98

避免更新Demo

React 使用虚拟 DOM,它是在浏览器中的 DOM 子树的渲染描述,这个平行的描述让 React 避免创建和操作 DOM 节点,
这些远比操作一个 JavaScript 对象慢。当一个组件的 props 或 state 改变,
React 会构造一个新的虚拟 DOM 和旧的进行对比来决定真实 DOM 更新的必要性,
只有在它们不相等的时候,React 才会使用尽量少的改动更新 DOM。
在此之上,React 提供了生命周期函数 shouldComponentUpdate,
在重新渲染机制回路(虚拟 DOM 对比和 DOM 更新)之前会被触发,赋予开发者跳过这个过程的能力。这个函数默认返回 true,让 React 执行更新。
shouldComponentUpdate (nextProps, nextState) {
return true;
}
返回true时就会调用componentDidUpdata() {

}
切住,React 会非常频繁的调用这个函数shouldComponentUpdate(){},所以要确保它的执行速度够快。

假如你有个带有多个对话的消息应用,如果只有一个对话发生改变,如果我们在 ChatThread 组件执行 shouldComponentUpdate,React 可以跳过其他对话的重新渲染步骤。
我们可以简单的实现 shouldComponentUpdate 如下:

1
2
3
shouldComponentUpdate (nextProps, nextState) {
return this.props.value !== nextProps.value;
}