乐趣区

React性能优化:PureComponent的使用原则

React15.3 中新加了一个 PureComponent 类,取代之前的 PureRenderMixin , PureComponent 可以进行 React 性能优化,减少不必要的 render 渲染次数,使用时只要把继承类从 Component 换成 PureComponent。
PureComponent 的原理是继承了 Component 类,自动加载 shouldComponentUpdate 函数,当组件更新时,shouldComponentUpdate 对 props 和 state 进行了一层浅比较,如果组件的 props 和 state 都没有发生改变,render 方法就不会触发,省去 Virtual DOM 的生成和对比过程,达到提升性能的目的。
下面从 PureComponent 的使用和源码来彻底了解它.
shouldComponentUpdate 优化性能
在 PureComponent 之前,我们经常看到优化 react 性能最常见的手段之一就是在 react 的生命周期函数 shouldComponentUpdate 里判断 props 或 state 的数据是否发生变化,通过返回 ture(更新) 和 false(不更新) 来阻止不必要的 render.
首先来看看 react 产生不必要渲染的一个场景:
用 create-react-app 来初始化 react 的运行环境,然后在 App.js 文件里创建一个定时任务,每隔一秒更新数据:
import React, {Component} from ‘react’
import ShouldComponentUpdateList from ‘./ShouldComponentUpdateList’

// 容器组件
export default class App extends Component {
state = {
data: []
}
componentDidMount() {
// 定时任务,每隔一秒更新数据
setInterval(() => {
this.setState({
data: [
{title: ‘react line 1’},
{title: ‘react line 2’},
]
})
}, 1000)
}
render() {
return(
<div>
{
this.state.data.map((item, index) => (
<ShouldComponentUpdateList key={index} list={item} />
))
}
</div>
)
}
}
ShouldComponentUpdateList 组件内容为:
export default class List extends Component {
render() {
console.log(‘list render’)
return(
<div>{this.props.list.title}</div>
)
}
}
命令行运行 npm start,浏览器查看输出:
<img src=’https://user-gold-cdn.xitu.io…;h=231&f=png&s=24814′>
发现控制台每隔一秒都会输出 list render,明明数据没有发生变化,但是 react 还是发生渲染,造成了不必要的渲染浪费。
只需要在 shuoldComponentUpdate 里加上判断,再次查看输出结果,定时任务的数据没有发生改变,不会再渲染 render 函数:
export default class List extends Component {
// 在 shuoldComponentUpdate 里判断 props 传递的数据没有发生变化,则不需要 render
shouldComponentUpdate(nextProps) {
// 返回值为 true 则 render,为 false 则不 render
if(nextProps.list.title === this.props.list.title) {
return false
}
return true
}
render() {
console.log(‘list render’)
return(
<div>{this.props.list.title}</div>
)
}
}
PureComponent 使用
除了使用 shouldComponentUpdate 来判断是否需要更新组件,还可以用 PureComponent, PureComponent 实际上自动加载 shouldComponentUpdate 函数,当组件更新时,shouldComponentUpdate 对 props 和 state 进行了一层浅比较.
新建 PureComponentList 组件,用 PureComponent 代替 Component:
import React, {PureComponent} from ‘react’
export default class List extends PureComponent {
render() {
console.log(‘list render’)
return(
<div>{this.props.list.title}</div>
)
}
}
在 App 组件中传入:
this.state.data.map((item, index) => (
<PureComponentList key={index} list={item}/>
))
然后查看浏览器输出结果,惊奇地发生,PureComponent 并没有阻止不必要 render,这是为什么呢?因为前面我们说到 PureComponent 的 shouldComponentUpdate 只对 props 和 state 进行浅比较,也就是 this.props = {list: { title: ‘react line1’} },nextProps = {list: { title: ‘react line1’} },作浅比较的话 this.props 当然不等于 next.props.
为了更清晰地找到原因,我们先来看看 PureComponent 的源码.
PureComponent 源码
首先找到 PureComponent 这个函数,在构造函数和原型上分别继承了 Component 的属性和方法:
export default function PureComponent(props, context) {
Component.call(this, props, context)
}

PureComponent.prototype = Object.create(Component.prototype)
PureComponent.prototype.constructor = PureComponent
PureComponent.prototype.shouldComponentUpdate = shallowCompare

function shallowCompare(nexProps, nextState) {
return !shallowEqual(this.props, nextProps) || !shollowEqual(this.state, nextState)
}
接着 PureComponent 在生命周期函数里面写了 shallowCompare 方法,shallowCompare 里面通过 shallowEqual 的返回值来返回 ture 还是 false.
接着来看看 shallowEqual 函数,源码地址:
export default function shallEqual(objA, objB) {
// 从后面代码可以看出,对于两个对象的比较为这里的代码
if (objA === objB) {
return true;
}

if (typeof objA !== ‘object’ || objA === null ||
typeof objB !== ‘object’ || objB === null) {
return false;
}

const keysA = Object.keys(objA);
const keysB = Object.keys(objB);

if (keysA.length !== keysB.length) {
return false;
}

// Test for A’s keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (!objB.hasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
}

return true;
}
从上面浅比较的源码 shallowEqual 函数可以看出,shallEqual 对于对象的比较仅仅通过 if (objA === objB) {return true;} 来判断,而 let a = {list: { title: ‘react line1’} },let b = {list: { title: ‘react line1’} }, a === b 值为 false,所以这就很好的解释了上面 PureComponent 并没有阻止不必要 render 的原因。
所以我们来改进代码,使得 PureComponent 的 props 如果传入对象情况下应该如何起效:
在 App.js 里面修改 PureComponentList 组件传入 item.title 而不是 item 对象,浏览器只输出两次 log:
this.state.data.map((item, index) => (
// <PureComponentList key={index} list={item}/>
<PureComponentList key={index} title={item.title}/>
))
通过解构 item 对象,传入 item.title,这样就可以进行浅比较,来达到优化不必要渲染的目的.
PureComponent 原则
由上面探究 PureComponent 源码我们知道,PureComponent 的组件在 props 或者 state 的属性值是对象的情况下,并不能阻止不必要的渲染,是因为自动加载的 shouldComponentUpdate 里面做的只是浅比较,所以想要用 PureComponent 的特性,应该遵守原则:

确保数据类型是值类型
如果是引用类型,不应当有深层次的数据变化 (解构).

React.memo
在使用 PureComponent 的时候,只能把 react 组件写成是 class 的形式,不能使用函数的形式;react v16.6.0 之后,可以使用 React.memo 来实现函数式的组件,也有了 PureComponent 的功能。
List 组件的 PureComponent:
const ListComponent = React.momo(() => (
<div>{this.props.data || ‘loading’}</div>
))
注:上面涉及到的所有源代码

退出移动版