This article introduces the differences between React PureComponent and Component, and the problems to be paid attention to when using it.
preface
Let’s start with PureComponent. The React Component inherits from Component. PureComponent is a purer Component that performs a shallow comparison of the data before and after the update. Components are rerender only if the data has actually changed. This can greatly improve the performance of components.
Compare Component and PureComponent
Inheriting Component creates a Component
App.js
The state property has two properties, text being the base data type and todo being the reference type. Compare the two data types respectively:
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: 'learning React'}}}/** * The function to modify the state property */
changeText = (a)= > {
this.setState({
text: 'World'
});
}
/** * modify state todo object function */
changeTodo = (a)= > {
this.setState({
id: 1.message: 'learning Vue'
});
}
render() {
// Print log to view rendering
console.log('tag'.'render');
const { text, todo } = this.state;
return (
<div className="App">
<div>
<span>Text: {text}</span>
<button onClick={ this.changeText} >Change the words</button>
</div>
<br />
<div>
<span>Plan: {todo.message}</span>
<button onClick={ this.changeTodo} >To change the plan</button>
</div>
</div>); }}export default App;
Copy the code
In-browser interface
test
Run the project, open the console, and see only log: Tag Render
-
Click the · Change text · button five times, and you can see that the console prints another five logs, and the Hello text in the browser changes to World
-
Click the “Change plan” button 5 times and the console prints 5 more logs. The “Learn React” plan in the browser becomes “learn Vue”
In fact, only one out of five clicks is valid, and the data hasn’t really changed, but setState() is still used, so it will still render again. So this mode is performance-intensive.
Inheritance PureComponent
The use of PureComponent is the same as that of Component, but instead of inheriting Component, PureComponent is used.
App.js
.// The code above is the same as before
class App extends PureComponent {
// The following code is the same as before. }export default App;
Copy the code
In-browser interface
test
Same as the above Component tests
-
Click the “Change text” button five times, and you can see that the console only prints ** log one more time, and the “Hello” text in the browser becomes “World”
-
Click the “Change Plan” button five times and the console prints only one more log. The “learn React” plan in the browser becomes “learn Vue”
As you can see, using PureComponent is a performance saver. Even with setState(), components are rerendered only when the data actually changes
Problems that may be encountered during use
Let’s change the changeText and changeTodo methods in our code
/** * The function to modify the state property */
changeText = (a)= > {
let { text } = this.state;
text = 'World';
this.setState({
text
});
}
/** * modify state todo object function */
changeTodo = (a)= > {
let { todo } = this.state;
todo.message = "Learning Vue";
this.setState({
todo
});
}
Copy the code
Now let’s test again:
-
Click the “Change text” button, the console prints log one more time, and the “Hello” text in the browser becomes “World”
-
** Note: ** When you click the change Schedule button, no log is printed on the console, and no plans are changed in the browser
Why call setState() without rendering when the message property in Todo has been changed? This is because PureComponent, when calling the shouldComponent lifecycle, does a shallow comparison of the data to see if it has changed, returns false if it has changed, and returns true if it has. So how does this shallow comparison mechanism work? Let’s look at the following source code analysis, to analyze.
PureComponent source code parsing
ReactBaseClasses. Js (Github code location)
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;
Copy the code
You can see that PureComponent is used the same way as Component, except that an isPureReactComponent property is added to it at the end. ComponentDummy passes methods and properties from the Component stereotype to the PureComponent through prototype mock inheritance. Meanwhile, to avoid the performance cost of attribute look-up due to the long prototype chain, the object. assign attribute is copied from Component.
But this is just the declaration creation of the PureComponent. It doesn’t show how to compare updates.
ReactFiberClassComponent. Js (Github code location)
function checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext,) {...PureComponent = isPureReactComponent; PureComponent = isPureReactComponent
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
}
Copy the code
ShallowEqual is a utility method in the Share package. Take a look at its internal implementation.
ShallowEqual. Js (Github code location)
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 (
typeofobjA ! = ='object' ||
objA === null ||
typeofobjB ! = ='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;
Copy the code
It also calls the IS function, which is also a utility method in the Share package.
ObjectIs. Js (Github code location)
/** * 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;
Copy the code
PureComponent source code summary
PureComponent has an isPureReactComponent property that is true. PureComponent has an isPureReactComponent property that is true. When checkShouldComponentUpdate, according to this property to determine whether PureComponent, if so, will be based on! shallowEqual(oldProps, newProps) || ! ShallowEqual (oldState, newState) the return value of the statement is used as the update basis. So, looking at the source files for shallowEqual and objectIs, we can draw a shallow comparison of the PureComponent:
-
The is function is used to check whether the two parameters are the same. If they are, the component is not updated.
- From the objectis.js code, the base attribute type determines whether the values are the same (including NaN), and the reference data type determines whether the values are a reference
-
If the is function evaluates to false, it evaluates whether both parameters are objects and neither is null. If either is not an object or either is null, it returns false, updating the component
-
If the first two keys pass, it can be determined that the two parameters are both objects. Then it can be determined whether the length of keys is the same. If they are different, it can directly return false, that is, update the component
-
If keys are different in length, the first level attributes of the two objects are compared, true if they are identical, false if any of the attributes are different
conclusion
After reading the source code, we can see why the components are not updated even though the data changes after we modify the logic of the changeTodo method. PureComponent references the same object by default and does not update the component. This is why this trap occurs.
-
Comparing PureComponent with Component shows that PureComponent performs better and generally updates with several valid changes
-
To avoid the pitfalls mentioned above, it is recommended to use React and Immutable. Js because Immutable data types are not the same in each variable. However, since learning Immutable is expensive, you can use the immutability-Helper plugin in your project to achieve a similar function. For more information on the use of immutability-Helper, check out my other blog post: Basic Use of immutability-Helper
-
While PureComponent improves performance, it only makes a superficial comparison of data, and the best way to optimize performance is to implement the response logic yourself in the shouldComponent lifecycle
-
For a summary of shallow PureComponent comparisons, see the PureComponent source code analysis summary above