One, the strangeness
In React, setState is so strange that beginners will assume it must be asynchronous, but in some cases it will be synchronous, which can be confusing.
On the first code
import React from 'react';
export default class StateDemo extends React.Component {
state = {
count: 0};// Update asynchronously
onChangeAsyncState = () = > {
this.setState({
count: this.state.count + 1});console.log(this.state.count);
};
onResetState = () = > {
this.setState({
count: 0});console.log(this.state.count);
};
// Synchronize updates
onChangeSyncState = () = > {
setTimeout(() = > {
this.setState({
count: this.state.count + 1});console.log(this.state.count);
}, 0);
};
render() {
return (
<div>
<p>Click the button below to change count</p>
<button onClick={this.onChangeAsyncState}>Asynchronous update :{this.state.count}</button>
<button onClick={this.onResetState}>reset</button>
<button onClick={this.onChangeSyncState}>Update :{this.state.count}</button>
</div>); }}Copy the code
1.1 Asynchronous Scenario
This is to clickAsynchronous update buttonClick count, then count becomes 1, but the console output is still 0
1.2 Synchronization Scenario
When the sync update button is clicked, the output is a bit surprising, with state directly synchronizing updates
1.3 Merge Updates
In addition to the above two scenarios, state also has a merge update operation, as shown in the following code
// Merge updates
onChangeMergeState = () = > {
this.setState({
count: this.state.count + 1});console.log(this.state.count);
this.setState({
count: this.state.count + 1});console.log(this.state.count);
};
render() {
console.log('render'.this.state.count);
return (
<div>
<p>Click the button below to change count</p>
<button onClick={this.onChangeMergeState}>Merge update :{this.state.count}</button>
</div>
);
}
Copy the code
When the merge button is clicked, the value of state does not change to 2, but to 1. Although two operations are done, a merge is done, and when the task queue is finally executed, the state is actually only updated once. Note that console.log(‘render’, 1) only executes once
[
](react-ts-lchssi.stackblitz.io)
Two, the principle of design
The core function of setState is to update state. Once state changes the state, it will trigger component re-rendering and finally update the view UI.
In 2017, Michel Weststrate, the author of MOBxJS, raised an issue: Why setState is asynchronous? React core member Dan Abramov gave an explanation, as well as a few early hints about what features React will bring
- ** Keep internal state consistent ** : Updates to props are asynchronous, if state is updated synchronously, that will cause new problems
- Enable concurrent updates for the future: assign different priorities to events based on their type (17) and process them concurrently to improve rendering performance.
On 1 May 2020, React official Andrew Clark () submitted a PR for Lanes, which mentioned lane priorities and lane task concurrency. React 17 is now available.
The above demo, and some of the original design principles, so it’s specific code is what?
Three, source code interpretation
React’s core principle was that the entire UI is a function that accepts states or data and then returns the entire rendered UI
In mathematical terms, it is UI= FN (state)UI= FN (state)UI= FN (state) in a sentence: The State is responsible for calculating the Reconciler’s state, and the FN is responsible for rendering the state in the UI View.
So this.setState calls the Reconciler (Diff algorithm), which is used to calculate the changes in the state and finally the Renderer.
The diff algorithm is not the focus of this article, but the update logic of this.setState.
The following is a comment to the setState source code, to be clear, there is no guarantee that it will be executed immediately, as it may be executed concurrently.
/** * Sets a subset of the state. Always use this to mutate * state. You should treat `this.state` as immutable. * * There is no guarantee that `this.state` will be immediately updated, so * accessing `this.state` after calling this method may return the old value. * * There is no guarantee that calls to `setState` will run synchronously, * as they may eventually be batched together. You can provide an optional * callback that will be executed when the call to setState is actually * completed. * * When a function is provided to setState, it will be called at some point in * the future (not synchronously). It will be called with the up to date * component arguments (state, props, context). These values can be different * from this.* because your function may be called after receiveProps but before * shouldComponentUpdate, and this new state, props, and context will not yet be * assigned to this. * *@param {object|function} partialState Next partial state or function to
* produce next partial state to be merged with current state.
* @param {? function} callback Called after state is updated.
* @final
* @protected* /
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null.'setState(...) : takes an object of state variables to update or a ' +
'function which returns an object of state variables.',);this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Copy the code
This. updater This is a ReactNoopUpdateQueue object, which is literally an update queue.
And then I’m going to go down,
Github.com/facebook/re…
2107 lines of code decide to do the operation of synchronization, so when does the expirationTime become Sync ni?
Github.com/facebook/re…
It is clear that all execution is synchronous except in concurrent mode, so when is it in concurrent mode?
React is annotated in the source code and is synchronized in Legacy modeGithub.com/facebook/re…
Is that Legacy now? inUsing Concurrent modeReactdom.render (rootNode) via this call is Legacy.
So why isn’t some code synchronous update? Because there’s one more parameter value that’s really important when you’re actually updating your code, called isBatchingUpdates, which if it’s true will go into merge updates, which will put the event into a deferred queue for asynchronous updates, and if it’s false will go straight into synchronous updates.
IsBatchingUpdates will automatically update to true during the React life cycle and during compositing events
Github.com/facebook/re…
Four,
In Legacy mode, asynchronous mode is used when React is out of context. In Legacy mode, asynchronous mode is used when React is out of context. However, in concurrent mode they are definitely all asynchronous.
Let’s change the previous case to execute in concurrent mode
// render(
, document.getElementById('root')); / / Legacy mode
createRoot(document.getElementById('root')).render(<App />); / / concurrent mode
Copy the code
The output is now exactly the same as the previous asynchronous update.
However,Concurrent modeBefore version 17, you needed unstable_ to use it, but not after version 17.
After a careful study of the source code, found that noConcurrent mode can also be usedThe unstable_batchedUpdates function explicitly changes isBatchingUpdates to true, so that events can be queued for execution. If you are interested, you can try it.
The above source code is based on this version 16.8.6. In fact, after version 17, this piece of logic has been updated based on the latest Lanes algorithm. The former is based on Lanes, and the later is based on Lanes.
The last
The above code, has been uploaded to the stackblitz. IO, interested can directly click the following link to myself feel stackblitz.com/edit/react- under…
Reference links
- RFClarification: why is setState asynchronous?
- Using Concurrent mode
- Initial Lanes implementation
- UI is a function of state