preface

The code used in this article is based on React V16.11.0

After a while of project practice, I haven’t made a summary of React data flow. This time, I learned more about it while studying Teacher Chen Yi’s In-depth Review of React Technology Stack (the React version is V15). At the same time, I read more articles and added practical codes

React One-way data streams and render() functions

This is commonly called a “top-down” or “unidirectional” data flow. Any state is always owned by some specific component, And any data or UI derived from that state can only affect components “below” them in the tree. — React — state and Lifecycle #The Data Flows Down

Top-down, one-way data flow is one of the characteristics of React. State is owned by the corresponding component, and state can only be passed down. When a component uses setState internally to update state data, the component calls the Render () method because we have changed the internal state of the component.

By default (shouldComponentUpdate returns true by default), the sub-component of the component also calls the render function to construct the Fier Tree(the virtual DOM). It’s up to Fiber to judge

The React Elements Tree was built to help explain React in its early days and has been called the “virtual DOM “. However, the concept of virtual DOM can be confusing (e.g. RN on IOS and Android), so it no longer appears in the official documentation, but can be found in the FAQ. Virtual DOM and Internals. All are called Fiber Trees in this article

I used the console.log() flag in the render function, and you can open the code to see: CodePen open

// App component: contains Father1 component and Father2 component
class App extends React.Component{... render(){console.log("App render!");
    return(
      <div>
        <Father1 />
        <Father2 />
      </div>); }}// Father1 component: no state
class Father1 extends React.Component{
  constructor(props) {
    super(props);
  }
  render(){
    console.log('Father1 render! ')
    return(
      <h1>Father1</h1>)}}// Father2 component: timer update state after mount, including Son1 component and Son2 component, Son1 component receives father2Data as props
class Father2 extends React.Component{
  constructor(props) {
    super(props);
    this.state = {
      father2Data:'original father2Data'
    }
  }
  componentDidMount(){
    setTimeout((a)= >{
      this.setState({
        father2Data:'update father2Data! '
      });
    }, 2000);
  }
  render(){
    console.log(this.state);
    console.log('Father2 render! ');
    return (
      <div>
        <h1>Father2</h1>
        <Son1 data={this.state.father2Data} />
        <Son2 />
      </div>}} // Son1 extends React.Component{... render(){ console.log('Son1 render! '); return(<p>Son1, which receives from the Father2 component<strong>father2Data: {this.props.data}</strong></p>}} // Son2 extends React.Component{... render(){ console.log('Son2 render! ') return(<p>Son2</p>)}}Copy the code

Take a look at the initial interface and console:


Timer 2000ms to update Father2 component state, let’s look at the interface and console

You can see that the Father2, Son1, and Son2 components call the Render function to build the Fiber Tree.

Fiber then compares components or element types, etc., to determine how to render the actual DOM, but that is beyond the scope of this article.

Use PureComponent for some optimization

ShouldComponentUpdate (shouldComponentUpdate) (shouldComponentUpdate) (shouldComponentUpdate) (shouldComponentUpdate) (shouldComponentUpdate) (shouldComponentUpdate) (shouldComponentUpdate) (shouldComponentUpdate) (shouldComponentUpdate)

It would have been nice to compare the props and state before and after, but that was expensive, so PureComponent was officially introduced. PureComponent is called PureComponent because you want it to have the same output if it has the same input (props, state).

Its shouldComponentUpdate method only makes a shallow comparison, somewhat reducing unnecessary Fiber Tree rerendering

React.PureComponent is similar to React.Component. The difference between them is that React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.

If your React component’s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.

class Son2 extends React.PureComponent{
  constructor(props) {
    super(props);
  }
  render(){
    console.log('Son2 render! ')
    return(
      <p>Son2</p>)}}Copy the code

CodePen open

PureComponent Precautions

Because PureComponent is a shallow comparison, pay special attention to the use of objects and array types in state, as illustrated below

  • The reference to the props object changed
  • SetState operates on arrays and objects
  • Props. Children was used to set up the child component

Let’s point out some examples based on the code

The reference to the props object changed

PureComponent also executes render when the address of the object passed by the parent component to the child component props changes

class Father2 extends React.Component{
   / /...
  render(){
    console.log(this.state);
    console.log('Father2 render! ');
    const test = {}; // The address of the test variable changed when the parent called render
    return(<div> <h1>Father2</h1> <Son1 data={this.state.father2Data} /> <Son2 data={test}/> Son2 also calls render </div>)}}Copy the code

CodePen open

If objects and arrays passed by props do not need to change dynamically, you can place references in state or objects

class Father2 extends React.Component{
  constructor(props) {
    super(props);
    this.state = {
      father2Data:'original father2Data'.test:{}
    }
    / / or
    // this.state = {
    // father2Data:'original father2Data'
    // };
    // this.test = {};
  }
  
  / /...
  render(){
    console.log(this.state);
    console.log('Father2 render! ');
    return(<div> <h1>Father2</h1> <Son1 data={this.state.father2data}/> <Son2 data={this.state.test}/> // or // <Son2 data= {this.test}/> </div>) } }Copy the code

CodePen open

This way, the address of the object or array does not change and will not cause the Fiber Tree to be rendered again

SetState operates on arrays and objects

In PureComponent, if the reference addresses are not changed when setState is called, components are not refreshed, resulting in UI inconsistency with state.

class Example extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      array: ['aa'.'bb']  // Initial content
    };
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(e) {
    console.log("-- -- -- e. arget. Value -- -- -- --");
    console.log(e.target.value);
    const a = this.state.array;  // 1. The array address is not changed
    a.push(e.target.value)
    this.setState({
      array: a  // 2. Render is not called again, so the UI is not refreshed
    });
    console.log("After ——————setState, this.state——————");
    console.log(this.state);
    
  }
  render() {
    console.log("Example render!");
    return (
      <div>
        <p>Array concatenation: {this.state.array}</p>
        <input onChange={this.handleChange} />
      </div>); }}Copy the code

CodePen open

As you can see, even though the state.array content has changed, its reference address has not changed. ShouldComponentUpdate returns false because it is a shallow comparison, so render is not generated

There are two ways to deal with this: 1. Use immutable.js 2. Use array.prototype. concat() to return a new Array without altering the original Array

Here we use immutab.js to demonstrate

class Example extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      list: Immutable.List(['aa'.'bb']) / / use the Immutable
    };
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(e) {
    console.log("-- -- -- e. arget. Value -- -- -- --");
    console.log(e.target.value);
    const oldList = this.state.list;
    const newList = oldList.push(e.target.value); //Immutable.List returns a new List after push, not the same address as the OldList, and the contents of OldList remain unchanged
    this.setState({
      list: newList
    });
    console.log("After ——————setState, this.state——————");
    console.log(this.state);
    
  }
  render() {
    console.log("Example render!");
    return (
      <div>
        <p>Array concatenation contents: {this.state.list}</p>
        <input onChange={this.handleChange} />
      </div>); }}Copy the code

CodePen open

Immutable Data cannot be changed once it is created, and a new Immutable object is returned when it is modified, added, or deleted. Because of this property, Immutable Data has many applications in pure functions as well.

Props. Children was used to set up the child component

ShouldComponentUpdate should return true when we set the props. Children child

class ItemList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      test:"origin data"
    };
  }
  componentDidMount(){
    setTimeout((a)= >{
      this.setState({
        test:"updated data"})},1000);
  }
  render() {
    console.log("ItemList render!");
    return (
      <div>
        <h1>ItemList</h1>
        <Item>
          <p>Jonithan</p>
        </Item>
      </div>); }}class Item extends React.PureComponent {
  // ...
  render() {
    console.log('Item render! ');
    return <div>{this.props.children}</div>; }}Copy the code

CodePen open

When the timer expires at 1000ms, the Item is rerendered even though it is a PureComponent. This is essentially the same as when a reference to the props object is changed. Because the JSX part of it:

<Item>
  <p>Jonithan</p>
</Item>
Copy the code

Translation:

<Item children = {React.createElement('p', {},'Jonithan')} / >Copy the code

The object reference address is different each time, so shouldComponentUpdate of PureComponent returns true

conclusion

I learned this part through Teacher Chen Yi’s “In-depth Review of React Technology Stack”, so I explored and read more articles, which is also a general summary of React data flow and Fiber. It is true that some people mentioned in the study article whether there are premature optimizations or small benefits, but I don’t think using PureComponent adds much code, just the issue of shallow comparisons, and the introduction of Immutable improves robustness and maintainable lines of code.

Private think specific or see the actual scene.

The resources

React — State and Lifecycle #The Data Flows Down React — Reconciliation React — React Only Updates What’s Necessary ReactJS – Does render get called any time “setState” is called? React.PureComponent React Fiber Architecture Inside Fiber: In depth Overview of the new Reconciliation algorithm in React React performance optimization