A few development often encountered problems

The following problems are often encountered in the actual development of the scenario, with a few simple examples of code to restore.

1. SetState is synchronous or asynchronous. Why can’t we get the update result immediately sometimes and can sometimes?

1.1 Hook functions and React composite eventssetState

Now there are two components

  componentDidMount() {
    console.log('parent componentDidMount');
  }

  render() {
    return (
      <div>
        <SetState2></SetState2>
        <SetState></SetState>
      </div>
    );
  }
Copy the code

Put the same code inside the component and put a synchronization delay code in Setstate1 componentDidMount to print the delay time:

  componentWillUpdate() {
    console.log('componentWillUpdate');
  }

  componentDidUpdate() {
    console.log('componentDidUpdate');
  }

  componentDidMount() {
    console.log('call SetState SetState);
    this.setState({
      index: this.state.index + 1
    })
    console.log('state'.this.state.index);
    
    console.log('call SetState SetState);
    this.setState({
      index: this.state.index + 1
    })
    console.log('state'.this.state.index);
  }
Copy the code

Here is the result:

Description:

  • 1. CallsetStateNot updated immediately
  • 2. All components use the same update mechanism, when all componentsdidmountAfter the parent componentdidmountThen perform the update
  • 3. Updates are merged for each component. Each component triggers only one update life cycle.

1.2 Asynchronous functions and native eventssetstate?

Call setState from setTimeout (the example has the same effect as in browser native events and interface callbacks)

  componentDidMount() {
    setTimeout(() => {
      console.log('call setState);
      this.setState({
        index: this.state.index + 1
      })
      console.log('state', this.state.index);
      console.log('call setState);
      this.setState({
        index: this.state.index + 1
      })
      console.log('state', this.state.index);
    }, 0);
  }
Copy the code

Execution Result:

Description:

  • 1. In the parent componentdidmountAfter the implementation
  • 2. CallsetStateSynchronous update

2. Why sometimes twice in a rowsetStateOnly once?

Execute the following code, respectively:

  componentDidMount() {
    this.setState({ index: this.state.index + 1= > {}, ()console.log(this.state.index);
    })
    this.setState({ index: this.state.index + 1= > {}, ()console.log(this.state.index); })}Copy the code
  componentDidMount() {
    this.setState((preState) = > ({ index: preState.index + 1= > {}), ()console.log(this.state.index);
    })
    this.setState(preState= > ({ index: preState.index + 1= > {}), ()console.log(this.state.index); })}Copy the code

Execution Result:

1
1
Copy the code
2
2
Copy the code

Description:

  • 1. Pass the object directlysetstateIt’s going to be merged into one
  • 2. Use function passstateWill not be merged

SetState Execution process

Since the source code is more complex, I will not post it here. If you are interested, you can clone a copy on Github and follow the following flow chart to go through.

1. The flow chart

Click if the picture is not clear

  • partialState:setStateThe first argument, object or function passed in
  • _pendingStateQueue: The current component waiting to be updatedstateThe queue
  • isBatchingUpdatesReact Indicates whether the system is in batch update state. All components use the react function
  • dirtyComponent: Queues of all components currently in the state to be updated
  • transcationReact’s transaction mechanism wraps n methods around methods called by transactionswaperObject and execute it at once:waper.init, called method,waper.close
  • FLUSH_BATCHED_UPDATES: used to perform updateswaperThere is only oneclosemethods

2. Execution process

According to the above flowchart, it can be roughly divided into the following steps:

  • 1. Pass in setStatepartialStateParameters are stored in the state staging queue of the current component instance.
  • 2. Check whether the React is in the batch update state. If yes, add the current component to the queue of the component to be updated.
  • 3. If the component is not in the batch update state, set the batch update status identifier to true and invoke the previous method again using transaction to ensure that the current component is added to the component queue to be updated.
  • 4. Call transactionwaperMethod to perform updates in sequence through the queue of components to be updated.
  • 5. Implement the lifecyclecomponentWillReceiveProps.
  • 6. Store the state of the component in the queuestateMerge, get the final state object to update, and leave the queue empty.
  • 7. Implement the lifecyclecomponentShouldUpdateDetermines whether to continue updating based on the return value.
  • 8. Implement the lifecyclecomponentWillUpdate.
  • 9. Perform real updates,render.
  • 10. Implement the lifecyclecomponentDidUpdate.

3. To summarize

1. Hook functions and compositing events:

During the React lifecycle and compositing events, React is still in its update mechanism, at which point isBranchUpdate is true.

No matter how many times setState is called, the update is not performed. Instead, the state to be updated is stored in _pendingStateQueue and the component to be updated is stored in dirtyComponent.

When the last update mechanism completes, for example in the lifecycle, all components, the topmost component didmount, will set isBranchUpdate to false. The setState accumulated before will be executed.

2. Asynchronous functions and native events

In view of the execution mechanism, setState itself is not asynchronous, but if setState is called, if React is in the update process, the current update will be temporarily stored and executed after the last update. This process gives the illusion of asynchrony.

In the life cycle, according to the asynchronous mechanism of JS, the asynchronous function will be temporarily stored and executed after the completion of all synchronous code execution, then the last update process has been completed, isBranchUpdate is set to false, according to the above process, then call setState can immediately execute the update and get the update result.

3.partialStateMerging mechanism

Let’s look at the code for _processPendingState in the process. This function is used to merge the state temporary queue and return a merged state.


  _processPendingState: function (props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;

    if(! queue) {return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];
    }

    var nextState = _assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
    }

    return nextState;
  },
Copy the code

Let’s just focus on the following code:

 _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
Copy the code

If an object is passed in, it will obviously be merged into one:

Object.assign(
  nextState,
  {index: state.index+ 1},
  {index: state.index+ 1})Copy the code

If a function is passed in, the function argument preState is the result of the previous merge, so the calculation is accurate.

4.componentDidMountcallsetstate

In componentDidMount(), you can immediately call setState(). It will trigger an additional rendering, but it will happen before the browser refreshes the screen. This ensures that even though Render () will be called twice in this case, the user will not see the intermediate state. Use this pattern with caution, as it often leads to performance problems. In most cases, you can use the assignment initial state instead in constructor(). However, there are cases where this is necessary, such as modal boxes and tooltips. In this case, you need to measure the DOM nodes before you can render something that depends on size or position.

SetState = componentDidMount setState = componentDidMount setState = componentDidMount setState = componentDidMount ComponentDidMount is in the middle of an update, and a call to setState will render again in the future, resulting in unnecessary performance waste, most of which can be fixed by setting the initial value.

Of course in componentDidMount we can call the interface and change state in the callback, and that’s the right thing to do.

SetState is unavoidable in componentDidMount when the initial value of state depends on a DOM attribute.

5.componentWillUpdate componentDidUpdate

SetState cannot be called in either life cycle.

As you can easily see from the above flow chart, calling setState in them causes an infinite loop, causing the program to crash.

6. Recommended usage

The setState value is passed by a function when the setState is called, and the latest updated state is obtained in the callback function.