本文简要介绍了 React 中 PureComponent 与 Component 的区别以及使用时须要注意的问题,并在后面附上了源码解析,但愿对有疑惑的朋友提供一些帮助。css
先介绍一下 PureComponent,平时咱们建立 React 组件通常是继承于 Component,而 PureComponent 至关因而一个更纯净的 Component,对更新先后的数据进行了一次浅比较。只有在数据真正发生改变时,才会对组件从新进行 render。所以能够大大提升组件的性能。react
里面的 state 有两个属性,text 属性是基本数据类型,todo 属性是引用类型。针对这两种数据类型分别进行对比:git
import React, { Component, PureComponent } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props)
this.state = {
text: 'Hello',
todo: {
id: 1,
message: '学习 React'
}
}
}
/** * 修改 state 中 text 属性的函数 */
changeText = () => {
this.setState({
text: 'World'
});
}
/** * 修改 state 中 todo 对象的函数 */
changeTodo = () => {
this.setState({
id: 1,
message: '学习 Vue'
});
}
render() {
// 打印 log,查看渲染状况
console.log('tag', 'render');
const { text, todo } = this.state;
return (
<div className="App"> <div> <span>文字:{ text }</span> <button onClick={ this.changeText }>更改文字</button> </div> <br /> <div> <span>计划:{ todo.message }</span> <button onClick={ this.changeTodo }>更改计划</button> </div> </div>
);
}
}
export default App;
复制代码
运行项目,打开控制台,此时看到只有一个 log:tag render
github
点击 5 次 ·更改文字· 按钮,能够看到控制台再次多打印了 5 次 log,浏览器中的 Hello
文字变成了 World
浏览器
点击 5 次 ·更改计划· 按钮,控制台同样多打印 5 次 log,浏览器中的 学习 React
计划变成了 学习 Vue
函数
分析一下,其实 5 次点击中只有一次是有效的,后来的数据其实并无真正改变,可是因为依然使用了 setState(),因此仍是会从新 render。因此这种模式是比较消耗性能的。工具
其实 PureComponent 用法也是和 Component 同样,只不过是将继承 Component 换成了 PureComponent。oop
...
// 上面的代码和以前一致
class App extends PureComponent {
// 下面的代码也和以前同样
...
}
export default App;
复制代码
和上面 Component 的测试方式同样源码分析
点击 5 次 ·更改文字· 按钮,能够看到控制台只多打印了** 1 次** log,浏览器中的 Hello
文字变成了 World
post
点击 5 次 ·更改计划· 按钮,控制台只多打印了 1 次 log,浏览器中的 学习 React
计划变成了 学习 Vue
由此能够看出,使用 PureComponent 仍是比较节省性能的,即使是使用了 setState(),也会在数据真正改变时才会从新渲染组件
下面咱们将代码中 changeText
和 changeTodo
方法修改一下
/** * 修改 state 中 text 属性的函数 */
changeText = () => {
let { text } = this.state;
text = 'World';
this.setState({
text
});
}
/** * 修改 state 中 todo 对象的函数 */
changeTodo = () => {
let { todo } = this.state;
todo.message = "学习 Vue";
this.setState({
todo
});
}
复制代码
此时咱们再从新测试一下:
点击 ·更改文字· 按钮,控制台多打印一次 log,浏览器中的 Hello
文字变成了 World
**注意:**点击 ·更改计划· 按钮,控制台没有 log 打印,浏览器中的计划也没有发生改变
为何代码修改以后,明明 todo 里的 message 属性也已经发生变化了,调用 setState(),却不进行渲染了呢?这是由于 PureComponent 在调用 shouldComponent 生命周期的时候,对数据进行了一次浅比较,判断数据是否发生改变,没发生改变,返回 false,改变了,就返回 true。那这个浅比较的机制是怎么作的呢?咱们一块儿看下面源码解析,来分析一下。
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
/** * Convenience component with default shallow equality check for sCU. */
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
复制代码
能够看到 PureComponent 的使用和 Component 一致,只时最后为其添加了一个 isPureReactComponent 属性。ComponentDummy 就是经过原型模拟继承的方式将 Component 原型中的方法和属性传递给了 PureComponent。同时为了不原型链拉长致使属性查找的性能消耗,经过 Object.assign 把属性从 Component 拷贝了过来。
可是这里只是 PureComponent 的声明建立,没有显示如何进行比较更新的,那咱们继续看下面的代码。
function checkShouldComponentUpdate( workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext, ) {
...
// 这里根据上面 PureComponent 设置的属性 isPureReactComponent 判断一下,若是是 PureComponent,就会走里面的代码,将比较的值返回出去
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
}
复制代码
shallowEqual 是在 share
包中一个工具方法,看一下其中的内部实现吧。
import is from './objectIs';
const hasOwnProperty = Object.prototype.hasOwnProperty;
/** * Performs equality by iterating through keys on an object and returning false * when any key has values which are not strictly equal between the arguments. * Returns true when the values of all keys are strictly equal. */
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(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 (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
export default shallowEqual;
复制代码
这里面还调用了 is 函数,这个函数也是 share 包中的一个工具方法。
/** * inlined Object.is polyfill to avoid requiring consumers ship their own * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is */
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
export default is;
复制代码
由上面的源码能够发现,其实 PureComponent 和 Component 中的方法和属性基本一致,只不过 PureComponent 多了一个 isPureReactComponent 为 true 的属性。在 checkShouldComponentUpdate 的时候,会根据这个属性判断是不是 PureComponent,若是是的话,就会根据 !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState) 这个判断语句的返回值做为更新依据。因此,查看了 shallowEqual 和 objectIs 的文件源码,咱们能够得出 PureComponent 的浅比较结论:
先经过 is 函数判断两个参数是否相同,相同则直接返回 ture,也就是不更新组件。
若 is 函数判断为 false,则判断两个参数是否都为 对象 且 都不为 null,若任意一个 不是对象 或 任意一个为 null,直接返回 false,也就是更新组件
若前两个判断都经过,则可判定两个参数皆为对象,此时判断它们 keys 的长度是否相同,若不一样,则直接返回 false,即更新组件
若 keys 长度不一样,则对两个对象中的第一层属性进行比较,若都相同,则返回 true,有任一属性不一样,则返回 false
阅读源码以后,能够发现以前咱们修改了 changeTodo 方法的逻辑以后,为何数据改变,组件却依然不更新的缘由了。是由于修改的是同一个对象,因此 PureComponent 默认引用相同,不进行组件更新,因此才会出现这个陷阱,在使用的过程当中但愿你们注意一下这个问题。
对比 PureComponent 和 Component,能够发现,PureComponent 性能更高,通常有几回有效修改,就会进行几回有效更新
为了不出现上面所说的陷阱问题,建议将 React 和 Immutable.js 配合使用,由于 Immutable.js 中的数据类型都是不可变,每一个变量都不会相同。可是因为 Immutable 学习成本较高,能够在项目中使用 immutability-helper 插件,也能实现相似的功能。关于 immutability-helper 的使用,能够查看个人另外一篇博客:immutability-helper 插件的基本使用
虽然 PureComponent 提升了性能,可是也只是对数据进行了一次浅比较,最能优化性能的方式仍是本身在 shouldComponent 生命周期中实现响应逻辑
关于 PureComponent 浅比较的总结能够查看上面的PureComponent 源码分析总结