preface
React In-depth: An Exploration of UI Development At the same time also into their own experience in the development of some.
You may be wondering if reading this article will help you develop React related projects at work. Telling the truth won’t help much. This article will not teach you how to use a new technology. It will not help you improve your programming skills. It will improve your React knowledge, such as distinguishing concepts, understanding best practices, and so on. If you want to consider the value of these knowledge from a utilitarian perspective, it will be very helpful for your interview. The knowledge points in this article are often asked in the interview why I know, because I have suffered from them.
The React component’s life cycle is divided into mount, Update, and unmount, but how do we know which phase the component has entered? We only know this through the React component’s exposed hook function. A hook function is a function that executes at a particular stage, such as constructor, which is called only once during the component’s birth phase, which counts as a “hook.” On the other hand, when a hook function is called, it enters a certain life phase, so you can add some code logic to the hook function to execute at that particular phase. Of course this is not absolute; for example, the render function is executed at birth as well as at update. By the way, hooks are a class of design patterns in programming, like github’s Webhooks. As the name suggests, it’s also a hook. You can subscribe to github events via Webhook, and github will send POST requests to your service when events occur. With this feature, you can listen for new merge events to occur in the master branch, and if your service receives a message about the event, you can perform an example deployment.
We walk through each hook function in chronological order of stages.
For details on the birth stage, see the previous article “Inside the React Lifecycle (Part 1) : The Birth Stage (Mount)”
Update the stage
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()
The update phase is triggered in three ways:
-
Changing props: A component does not actively change the props property that it has. Its props property is passed to it by its parent component. Forcing a reassignment of props causes an error.
-
Change state: Changes to state are made through the setState interface. Also designing states is tricky, which states can and can’t be in there; What components can and cannot have state; There are certain rules to follow. This topic can be addressed separately when the opportunity arises
-
Call the forceUpdate method: This was mentioned in the previous phase, forcing the component to update.
setState
Is asynchronous
A large part of the reason components are updated is because the setState interface is called to update state. We often call setState synchronously, but the setState method is actually asynchronous. Take this code for example:
onClick() {
this.setState({
count: 1});console.log(this.state.count)
}Copy the code
In a component’s click event handler, we update the count in state and then immediately try to read the latest count. The fact is you’re not reading 1, 2 should be the previous value.
A more fatal error is code like this that calls setState continuously at the same block level
this.setState({ ... this.state,foo: 42 });
this.setState({ ... this.state,isBar: true });Copy the code
In this case, the value of foo set the first time will be overridden by the second setting and restored
componentWillReceiveProps(nextProps)
When passed to the component props change, component componentWillReceiveProps will be called, namely method of parameters passed is sending more changes after props value (usually we named nextProps). In this method, you can use this.props to access the current value of the property, and nextProps to access the value of the property to be updated, or compare them, or evaluate them to determine the state you need to update and call setState to update the state. Calling the setState method in this hook function does not trigger another rendering.
Interestingly, although the changes of props will cause componentWillReceiveProps call; But componentWillReceiveProps call does not mean that the props really changed. That’s not what I’m talking about, Facebook has A whole article about this :(A => B)! => (B => A). For example, look at the following component:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 1,}this.onClick = this.onClick.bind(this);
}
onClick() {
this.setState({
number: 1,
})
}
render() {
return (
<MyButton onClick={this.onClick} data-number={this.state.number} />); }}Copy the code
State is re-updated with the setState interface for each click event, but the value is the same each time, that is, number:1. And passes the state of the current component as a property to
Yes, even if the value is the same every time it is updated.
The reason for this is quite simple: React doesn’t know if the property passed in has changed. Why didn’t React try to make an equality judgment?
Because this is not possible, the new attribute and the old attribute may refer to the same memory area (reference type), so it is not accurate to use === to determine equality. One possible solution is to make a deep copy of the data and then compare it, but this is poor performance for large data structures and can run into the problem of circular references.
So the React to this change were exposed by a hook function, don’t think that when componentWillReceiveProps called means a modification of the props, if you need to change to do something, be sure to manually.
shouldComponentUpdate()
ShouldComponentUpdate is important to decide whether to continue the current life cycle. By default this function returns true to continue the current life cycle; You can also return false to terminate the current life cycle, preventing further render and subsequent steps.
As we said earlier, React does not make a deep comparison to props, and the same applies to state. ShouldComponentUpdate should be called again even if props and state are not changed, including the following steps componentWillUpdate, Render, and componentDidUpdate. This obviously takes a toll on performance.
The parameters passed to shouldComponentUpdate include props and state to be changed. The parameters are named nextProps and nextState. In this function you can also access the current state and props using this. So you are “all-knowing” here, and you can determine whether the props and state have changed based on your own business logic, and decide whether to proceed with the next step. ShouldComponentUpdate is usually the first step in optimizing React performance. This step of optimization not only optimizes the flow of the component itself, but also saves the cost of re-rendering the child components.
PureRenderMixin () ¶ PureRenderMixin () PureRenderMixin () ¶ PureRenderMixin () ¶ PureRenderMixin () ¶ PureRenderMixin () ¶ PureRenderMixin () ¶ PureRenderMixin () ¶
import PureRenderMixin from 'react-addons-pure-render-mixin'; // ES6
const createReactClass = require('create-react-class');
createReactClass({
mixins: [PureRenderMixin],
render: function() {
return <div className={this.props.className}>foo</div>; }});Copy the code
Minins are a mechanism supported by React that allows multiple components to share code. The PureRenderMixin plugin does a very simple job of overriding the shouldComponentUpdate function for you and performing a shallow comparison of objects, which can be found here and here.
You can also do this in ES6 by directly inheriting React.PureComponent instead of react.ponent. In the official words of React
React.PureComponent
is exactly likeReact.Component
, but implementsshouldComponentUpdate()
with a shallow prop and state comparison.
Pure
Again, PureComponent implements for you only a determination of whether a reference has changed, or even a simple determination of ===, so that’s why we call it pure. To illustrate, let’s take a practical example
/* MyButton.js: */
import React from 'react';
class MyButton extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
console.log('render');
return <button onClick={this.props.onClick}>My Button</button>}}export default MyButton;
/* App.js: */
import React from 'react';
import MyButton from './Button.js';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
arr: [1],}this.onClick = this.onClick.bind(this);
}
onClick() {
this.setState({
arr: [...this.state.arr, 2]}); } render() {return (
<MyButton onClick={this.onClick} data-arr={this.state.arr} />
);
}
}
export default App;Copy the code
In the example above, the ARR variable in State is modified with each click, and the reference and value of the ARR variable are changed. The important thing is that the MyButton component inherits the React.PureComponent. The log information in MyButton will be printed every time it is clicked, which will restart render each time
If we make some changes to the onClick method:
onClick() {
const arr = this.state.arr;
arr.push(2);
this.setState({
arr: arr,
})
}Copy the code
This method also changes the ARR variable, but only the value, not the reference, and when the button (MyButton) is clicked again, MyButton will not render again. That is, the PureComponent did a shallow comparison for us ahead of time.
Immutable data, which changes references but not data content, is often used as a way to optimize React. Immutable.js allows you to do this by modifying data in such a way that every time you modify the data, you actually get a new reference to the data, not the original data. At the same time, the reducer in Redux wants to achieve a similar effect. Reducer focuses on its pure and will not cause side effects when it is executed, that is, it avoids the modification of the incoming data reference and it is convenient to compare the update of the component state.
componentWillUpdate()
The componentWillUpdate method is similar to the componentWillMount method in that it is triggered just before rendering. Here you can get the nextProps and nextState, as well as the props and state that are currently expiring. You can store them for later use if you need them.
Unlike componentWillMount, you cannot use setState in this method, otherwise it will immediately trigger another render and call componentWillUpdate again, in an infinite loop.
componentDidUpdate()
Similar to the Mount phase, when a component enters the componentDidUpdate phase it means that the latest native DOM has been rendered and is accessible through Refs. This function passes in two arguments, prevProps and prevState, which, as the name implies, is the previous state. You can still access the current state with the this keyword because you can access native DOM relationships, and it’s also useful for doing things where a third party needs to manipulate the library.
In the update phase, the call sequence of hook functions is similar to that of the mount phase, especially componentDidUpdate, which takes precedence over the call of the parent component
Because we can access the DOM, we might need to get the actual element style from the hook function and write it to state. For example, your code might look like this:
componentDidUpdate(prevProps, prevState) {
// BAD: DO NOT DO THIS!!!
let height = ReactDOM.findDOMNode(this).offsetHeight;
this.setState({ internalHeight: height });
}Copy the code
If by default your shouldComponentUpdate() function always returns true, then updating state in componentDidUpdate will put us in an infinite render loop. If you must do this, you should at least cache the last result and conditionally update state:
componentDidUpdate(prevProps, prevState) {
// One possible fix...
let height = ReactDOM.findDOMNode(this).offsetHeight;
if (this.state.height ! == height ) {this.setState({ internalHeight: height }); }}Copy the code
Death phase
componentWillUnmount()
This hook function is triggered when a component needs to be removed from the DOM. There’s not much to note here, and there’s usually some “cleaning” related work done in this function
- Cancel all network requests that have been sent
- Remove the Event Listener from the DOM on the component
conclusion
Finally, this article is a summary of the open-source book React In-Depth: An Exploration of UI Development. Basically, if you want to understand the life cycle, you just need to read this one book. I hope this simplified Chinese version will also be helpful to you.
This article is also published in my Zhihu column, welcome your attention
reference
- do not extend React.Component
- React Elements vs React Components vs Component Backing Instances
- React.createClass versus extends React.Component
- (A => B) ! => (B => A)
- Beware: React setState is asynchronous!