This is the second day of my participation in the August More text Challenge. For details, see:August is more challenging
A friend recently had an interview and the interviewer asked the most bizarre question, which I wrote in the headline.
If you ask this question, you may not know much about the React React. If you read that you are familiar with the React 🤣, you can tell whether you are really familiar with the React 🤣.
Is the interviewer asking the right questions?
The interviewer asks if setState is a macro task or a microtask. In his mind, setState must be an asynchronous operation. To determine whether setState is an asynchronous operation, create a React project using CRA and edit the following code in the React project:
import React from 'react';
import logo from './logo.svg';
import './App.css';
class App extends React.Component {
state = {
count: 1000
}
render() {
return (
<div className="App">
<img
src={logo} alt="logo"
className="App-logo"
onClick={this.handleClick}
/>
<p>{this.state.count}</p>
</div>); }}export default App;
Copy the code
The page looks something like this:
The React Logo above is bound with a click event. Now we need to implement this click event. After clicking the Logo, a setState operation is performed, and a log is printed when the set operation is complete. The code is as follows:
handleClick = () = > {
const fans = Math.floor(Math.random() * 10)
setTimeout(() = > {
console.log('Macro Task triggering')})Promise.resolve().then(() = > {
console.log('Microtask triggering')})this.setState({
count: this.state.count + fans
}, () = > {
console.log('Number of new fans :', fans)
})
}
Copy the code
Obviously, after clicking the Logo, the setState action is completed, followed by the triggering of the microtask and the triggering of the macro task. Thus, the setState is executed before the microtask and macro task, and even that is only to say that the setState is executed before the promise.then, which does not prove that it is a synchronous task.
handleClick = () = > {
const fans = Math.floor(Math.random() * 10)
console.log('Up and running')
this.setState({
count: this.state.count + fans
}, () = > {
console.log('Number of new fans :', fans)
})
console.log('End run')}Copy the code
It looks like setState is another asynchronous operation. The main reason is that during React life cycle and bound event flow, all setState operations will be cached in a queue first. After the whole event or the mount process is finished, the cached setState queue will be taken out for calculation and state update will be triggered. As soon as we jump out of React’s event stream or lifecycle, we can break React’s grip on setstates. The easiest way to do this is to put the setState in the anonymous function of setTimeout.
handleClick = () = > {
setTimeout(() = > {
const fans = Math.floor(Math.random() * 10)
console.log('Up and running')
this.setState({
count: this.state.count + fans
}, () = > {
console.log('Number of new fans :', fans)
})
console.log('End run')})}Copy the code
Thus, the setState is essentially in an event loop, not switched to another macro or microtask. It is implemented in synchronous code, but behaves asynchronously. So, there is no interviewer problem at all.
How does React control setState?
In the previous case, setState only becomes like a synchronized method in setTimeout. How does this work?
handleClick = () = > {
// Normal operation
this.setState({
count: this.state.count + 1
})
}
handleClick = () = > {
// Break the React control
setTimeout(() = > {
this.setState({
count: this.state.count + fans
})
})
}
Copy the code
To review the previous code, we recorded the call stack once in Performance for each of the two operations to see how the call stack differed between the two.
In the call stack, you can see that the Component.setState method eventually calls the enqueueSetState method, which internally calls the scheduleUpdateOnFiber method, The difference is that the scheduleUpdateOnFiber method will only be called — ensureRootIsScheduled — and the flushSyncCallbackQueue method will be called once the event method is complete. – scheduleUpdateOnFiber will be called directly to flushSyncCallbackQueue after the ensureRootIsScheduled call is completed with the React stream; This method is used to update state and render again.
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
if (lane === SyncLane) {
// Synchronize operations
ensureRootIsScheduled(root, eventTime);
// Determine whether the React event stream is still active
// If not, call flushSyncCallbackQueue directly
if(executionContext === NoContext) { flushSyncCallbackQueue(); }}else {
// Asynchronous operation}}Copy the code
The above code simply describes this process by determining whether executionContext is equal to NoContext to determine whether the current update process is in the React event stream.
It is well known that When React binds an event, it will synthesize the event, bind it to the document (react@17 has changed to the DOM element specified when binding the event to Render), and then React will dispatch the event.
When all events are triggered, they first call batchedEventUpdates$1, where they change the executionContext value. React knows that the setState is in its control.
// The default state of executionContext
var executionContext = NoContext;
function batchedEventUpdates$1(fn, a) {
var prevExecutionContext = executionContext;
executionContext |= EventContext; // Modify the status
try {
return fn(a);
} finally {
executionContext = prevExecutionContext;
// Call flushSyncCallbackQueue at the end of the call
if(executionContext === NoContext) { flushSyncCallbackQueue(); }}}Copy the code
So either call flushSyncCallbackQueue directly or call flushSyncCallbackQueue later, it’s essentially synchronous. It’s just a matter of order.
There will be asynchronous setstates in the future
If you look at the code above, you’ll see that in the scheduleUpdateOnFiber method, it says, “Is lane synchronous? Is there an asynchronous situation?”
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
if (lane === SyncLane) {
// Synchronize operations
ensureRootIsScheduled(root, eventTime);
// Determine whether the React event stream is still active
// If not, call flushSyncCallbackQueue directly
if(executionContext === NoContext) { flushSyncCallbackQueue(); }}else {
// Asynchronous operation}}Copy the code
React was prepared for asynchrony when it upgraded the Fiber architecture two years ago. Concurrent mode will be released in React 18. The official introduction to Concurrent mode is as follows.
What is Concurrent mode?
Concurrent mode is a new set of features in React that help applications stay responsive and adjust appropriately based on the user’s device performance and network speed. In Concurrent mode, rendering is not blocked. It’s interruptible. This improves the user experience. It also unlocks new features that were not possible before.
Now, if you want to use Concurrent mode, you need to use the experimental version of React. If you’re interested in this section, you can read my previous post: Evolution of the React Architecture — From Synchronous to Asynchronous.