1. When is setState synchronized and asynchronously updated?
1.1 Event handlers controlled by React, and setStates in lifecycle functions are asynchronously updated. Multiple SETStates may be merged and updated.
Component 1 state has two variables, num and times, both of which start at 1. Click button to add num and times +1, respectively.
class Test1 extends Component { state = { num: 1, times: 1, } onClick = () => { console.log('state 1'); this.setState({ num: this.state.num + 1, }); console.log('state 2'); this.setState({ times: this.state.times +1, }); console.log('state 3'); } render() { console.log('render'); const { num, </button> <div> num:{num} </div> <div> times:{times} </div> </div> ); }}Copy the code
Console print result:
SetState updates asynchronously, and for 2 setState merge updates, render fires only once.
1.2 setState updates synchronously in events outside the React control, such as native JS binding events, asynchronously executed setTimeout/setInterval, promise.then (), etc.
For the same component, update the state in setTimeout:
onClick = () => {
setTimeout(() => {
console.log('state 1');
this.setState({
num: this.state.num + 1,
});
console.log('state 2');
this.setState({
times: this.state.times +1,
});
console.log('state 3');
}, 1000);
}
Copy the code
Console print result:
The same component updates the status in promise.then () :
onClick = () => { new Promise((resolve,reject) => { resolve(); }).then(() => { console.log('state 1'); this.setState({ num: this.state.num + 1, }); console.log('state 2'); this.setState({ times: this.state.times +1, }); console.log('state 3'); })}Copy the code
Console print result:
Add click events to button using native addEventListener
componentDidMount() { document.getElementById('btn').addEventListener('click',() => { console.log('state 1'); this.setState({ num: this.state.num + 1, }); console.log('state 2'); this.setState({ times: this.state.times +1, }); console.log('state 3'); })}Copy the code
Console print result:
React status update merge and how to control asynchrony and synchronization
2.1 Merge status updates
SetState can take two arguments. The first argument is an object or a function, and the second argument is a callback function that is executed after a state update, which can be regarded as a microtask.
The argument is the setState of the object:
onClick = () => {
console.log('state 1');
this.setState({
num: this.state.num + 1,
})
console.log('state 2');
this.setState({
num: this.state.num + 1,
})
console.log('state 3');
this.setState({
num: this.state.num + 1,
})
console.log('state 4');
}
Copy the code
Console print result:
When the button is clicked, setState is executed three times, but num is actually increased by 1. This is because setState is “asynchronous”, where this.num is equivalent to a snapshot and cannot be updated instantly. This.state. num is 1, so num is 2.
The argument is the setState of the function:
onClick = () => {
console.log('state 1');
this.setState( preState => ({
num: preState.num + 1,
}))
console.log('state 2');
this.setState( preState => ({
num: preState.num + 1,
}))
console.log('state 3');
this.setState( preState => ({
num: preState.num + 1,
}))
console.log('state 4');
}
Copy the code
Console print result:
When using a functional argument, which is considered “synchronous”, preState gets the value immediately updated, and after three updates, num is 4.
Combined with the following updates, the console prints what the results look like:
onClick = () => {
//a
this.setState({
num: this.state.num + 1,
})
console.log('1:',this.state.num);
//b
this.setState({
num: this.state.num + 1,
})
console.log('2:',this.state.num);
setTimeout(() => {
//c
this.setState({
num: this.state.num + 1,
});
console.log('3:',this.state.num);
}, 0);
//d
this.setState(preState => ({
num: preState.num + 1,
}),() => {
console.log('4:',this.state.num);
})
//e
this.setState(preState => ({
num: preState.num + 1,
}))
console.log('5:',this.state.num);
}
Copy the code
Console print result:
Update C in setTimeout, even if the delay time is 0, is also a macro task; The other four updates are merged, so there are actually two updates in total. The log in d is in the callback function, which is a microtask, so the order of 5 logs is 1, 2, 5, 4, 3.
Num = 1; num = 2; prestate. num = 2; Num = 4; num = 4;
In the second update, this.state.num was already 4, so num was 5.
(It is not recommended to mix the two parameter forms in practice.)
2.2 Synchronization Asynchronous control Policy
In the implementation of the setState function of Rect, there is a variable isBatchingUpdate. If it is true, it indicates that the state is in batch update mode and no state update operation is performed. Instead, add the component that needs to be updated to the dirtyComponents array; When false, the batchedUpdates update is performed on the queue. IsBatchingUpdates default to false, indicating that setState will update this.state; However, there is a function called batchedUpdates that changes isBatchingUpdates to true, When React calls the event handler, batchedUpdates will be called to change isBatchingUpdates to true, so that setState controlled by React events will not update this.state synchronously.
2.3 Converting an asynchronous update to a synchronous update
I searched on the Internet for the method to change the asynchronous update into synchronous update. Most of them use Promise to wrap setState and use async/await to achieve synchronous update state when calling.
onClick = async () => {
console.log('state 1');
await this.setStateAsync({
num: this.state.num + 1,
});
console.log('state 2');
await this.setStateAsync({
times: this.state.times +1,
});
console.log('state 3');
}
setStateAsync = (state) => {
return new Promise((resolve,reject) => {
this.setState(state,() => {
resolve()
})
})
}
Copy the code
Console print result:
This does change the asynchronous update state to synchronous update, but you don’t need to wrap setState with promises, just use async/await.
onClick = async () => {
console.log('state 1');
this.setState({
num: this.state.num + 1,
});
console.log('state 2');
this.setState({
num: this.state.num +1,
});
console.log('state 3');
await this.setState({
num: this.state.num +1,
});
console.log('state 4');
this.setState({
num: this.state.num +1,
});
console.log('state 5');
this.setState({
num: this.state.num +1,
});
console.log('state 6');
}
Copy the code
Console print result:
As can be seen, the first three setStates are still asynchronous updates and merged updates. After the third setState uses await, the next two setStates are synchronous updates.
You don’t even need to wait for setState, waiting for a Promise at random can have this effect:
onClick = async () => {
console.log('state 1');
this.setState({
num: this.state.num + 1,
});
console.log('state 2');
this.setState({
num: this.state.num +1,
});
console.log('state 3');
this.setState({
num: this.state.num +1,
});
await new Promise((resolve,reject) => {
console.log('promise start');
resolve();
})
console.log('promise end');
console.log('state 4');
this.setState({
num: this.state.num +1,
});
console.log('state 5');
this.setState({
num: this.state.num +1,
});
console.log('state 6');
}
Copy the code
Console print result:
The three setState updates before await are asynchronous and the two setState updates after await are synchronous.
First we need to understand async/await. The async function returns a Promise object, and callbacks can be added using the then method. When a function executes, it returns an await, waits for the triggered asynchronous operation to complete, and then executes a statement inside the function. (Await is usually used to wait for a Promise, but it can also wait for a regular function expression, Waiting for a generic expression is equivalent to wrapping its return value with promise.resolve (). In other words, the statements after “await” in the function body are triggered asynchronously. At this time, the React schedule is already broken, so setState is updated synchronously.
To deepen understanding of async/await, please write a simple async/await interview question:
async function async1(){
console.log('async1 start');
let res = await async2();
console.log(res);
console.log('async1 end');
}
async function async2(){
console.log('async2 start');
return 'async2 end'
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise((resolve,reject) => {
console.log('Promise start');
resolve();
}).then(() => {
console.log('Promise end');
})
console.log('script end');
Copy the code
Console print result:
The main thread prints ‘script start’ and encounters setTimeout, and the function body enters the macro task.
Execute function async1, print ‘async1 start’, encounter await, enter function async2, print ‘async2 start’, return value ‘async2 end’ will be wrapped by promsie.resolve () and enter microtask;
Enter new Promsie(), print ‘Promise start’, and resolve(‘Promise end’) to enter the microtask
Print ‘script end’ and the main thread ends.
Microtask 1, function async2 returns, function async2 waits, prints’ asynC2 end’, ‘asynC1 end’.
Microtask 2, promise.then (), print ‘Promise end’. Microtask clearing.
Execute the macro task and print ‘setTimeout’.
reference
React setState synchronization asynchrony and merge
React setState Update state When is it synchronous and when is it asynchronous?