Generally, when we learn React, we will keep in mind that setState is asynchronous. There are two ways to get the latest state value (you can look at the complete code at the end and then come back):
1. Get the latest state in the callback function
reactClick = () => { const { cnt } = this.state this.setState({ cnt: cnt + 1 }, () => { console.log('after reactClick setState cnt is', this.state.cnt); console.log('reactClick end -----------------------'); }) console.log('reactClick now cnt is', cnt); }
2. Use async and await to get the latest state. (in fact, asynchronous is changed into synchronous, so I still classify this situation as asynchronous)
asyncClick = async () => { let res = await this.setState({ cnt: this.state.cnt + 1 }, () => { console.log('after asyncClick setState cnt is', this.state.cnt); }) console.log('asyncClick', res); // res yes undefined (Curious to see res What is it and why undefined,I'm going to see it react setState (source code) console.log('after asyncClick cnt is', this.state.cnt); console.log('asyncClick end -----------------------'); }
The following describes two situations in which React setState is synchronized:
1. Bind setState to the native js event
jsClick = () => { const { cnt } = this.state console.log('before jsClick cnt is', this.state.cnt); this.setState({ cnt: cnt + 1 }, () => { console.log('after jsClick setState cnt is', this.state.cnt); }) console.log('after jsClick cnt is', this.state.cnt); console.log('jsClick end -----------------------'); }
Here, you need to combine the code of jsClick and reactClick, and then look at the output order of the code separated by the red line.
First look at the output order of reactClick. There should be no big problem with the output order here. You can see that first output reactClick now cnt is 1, then execute render (cnt is output in render), and finally execute the callback function of setState. From here, we can see that after setState, we did not get the latest cnt, but got it in the callback function, so setState in this case (synthetic events of react, such as onClick, onBlur, etc., dom events rewritten by react itself) is a 100% asynchronous function.
Here, we need to remember that we first execute render, and then execute the callback function of setState (note the callback function of setState).
Next, let's focus on the output sequence of jsClick :
1. before jsClick cnt is 0
When the native event of the button is clicked, the setState has not been executed at this time. Output it first before jsClick cnt is 0 .
2. render cnt is 1 (react event)
Then it executes setState, triggers render, and outputs render cnt is 1
3. after jsClick setState cnt is 1 (react event)
Then, the callback function of setstate is executed to output after jsClick setState cnt is 1
4. after jsClick cnt is 1 (native js events)
Finally after jsClick cnt is 1
From the above output order, we can see that the render setState of react and the callback function of setState are executed earlier than the native js. Then it is not difficult to explain why setState is synchronized on native events.
Native events are executed after the react event is completed (to be used below). It should be added that here I understand the code executed after setState as a native js event. Then, the state we get after setState is the latest state, so it looks like a synchronization function.
2.setState is in setTimeout
timeOut = () => { console.log('before setTimeout cnt is',this.state.cnt); setTimeout(() => { this.setState({ cnt: this.state.cnt + 1 }, () => { console.log('after setTimeout setState cnt is', this.state.cnt); }) console.log('after timeOut', this.state.cnt) console.log('setTimeout end -----------------------'); }, 0) }
Output sequence of setTimeout:
1. before setTimeout cnt is 0
There should be no problem with the first output here. Start a setTimeout asynchronous task. Execute the synchronous task first, and then execute the asynchronous task after the synchronous task is completed.
2. render cnt is 1 (react event)
Then execute setState to trigger render output render cnt is 1
3. after setTimeout setState cnt is 1 (react event)
Then the callback function of setstate is executed, and the output after setTimeout setState cnt is 1
4. after timeOut 1 (native js event)
Finally, execute the native js and output after timeOut 1
We can use jsClick's conclusion that the native events are executed after the react event is completed to understand the output sequence. Output sequence 2 and output sequence 3 are react events, while output sequence 4 and setTimeout are js native events. Then executing setState in setTimeout is like executing setState in a native event. The execution mechanism of react is to execute the react event first, and then execute the js native event.
Conclusion:
Through the above example, it is inferred that react (not necessarily accurate, light spray). When react encounters its own event (setState, onClick), execute its own event first. After all your own events are executed (including some of your own callback functions such as setState), you can execute the native js events. This may be caused by the virtual DOM, because we know that using react to operate the elements on the page actually operates on the virtual dom.
When react renders a page, the real DOM is generated according to the virtual DOM, so the synthetic events of react are only written on the virtual DOM, not the real dom. Then, when operating the virtual DOM, we can only follow the execution mechanism of react (synchronous or asynchronous, etc.), so when generating the real DOM, the virtual DOM must be updated.
If the real dom displays itself according to the virtual dom, the update of the virtual dom must be completed first. Therefore, the react event must be completed. The js native event we will execute must be after the react event.
The following is the complete code of the sample:
import React, { Component } from 'react'; class App extends Component { state = { cnt: 0, } componentDidMount() { document.getElementById('JSBtn').addEventListener('click', this.jsClick) } jsClick = () => { const { cnt } = this.state console.log('before jsClick cnt is', this.state.cnt); this.setState({ cnt: cnt + 1 }, () => { console.log('after jsClick setState cnt is', this.state.cnt); }) console.log('after jsClick cnt is', this.state.cnt); console.log('jsClick end -----------------------'); } reactClick = () => { const { cnt } = this.state this.setState({ cnt: cnt + 1 }, () => { console.log('after reactClick setState cnt is', this.state.cnt); console.log('reactClick end -----------------------'); }) console.log('reactClick now cnt is', cnt); } timeOut = () => { console.log('before setTimeout cnt is',this.state.cnt); setTimeout(() => { this.setState({ cnt: this.state.cnt + 1 }, () => { console.log('after setTimeout setState cnt is', this.state.cnt); }) console.log('after timeOut', this.state.cnt) console.log('setTimeout end -----------------------'); }, 0) } asyncClick = async () => { let res = await this.setState({ cnt: this.state.cnt + 1 }, () => { console.log('after asyncClick setState cnt is', this.state.cnt); }) console.log('asyncClick', res); // res yes undefined (Curious to see res What is it and why undefined,I'm going to see it react setState (source code) console.log('after asyncClick cnt is', this.state.cnt); console.log('asyncClick end -----------------------'); } render() { console.log(`${this.state.cnt === 0 ? 'this is first(init) render' : 'render'} cnt is`, this.state.cnt); return ( <div> <button id='JSBtn'>JSBtn</button> <button onClick={this.reactClick}>ReactBtn</button> <button onClick={this.timeOut}>setTimeout</button> <button onClick={this.asyncClick}>asyncClick</button> </div> ); } } export default App;