Recently, a friend interviewed, and the interviewer asked a weird question, which is the question I wrote on the title.
To be able to ask this question, the interviewer should not be very familiar with React. It is also possible that the interviewer's resume has written that he is familiar with React. The interviewer wants to use this question to judge whether the interviewer is really familiar with React 🤣.
Is the interviewer’s questioning correct?
The interviewer’s question is, is setState
a macro task or a micro task, so in his perception, setState
must be an asynchronous operation. In order to determine whether setState
is an asynchronous operation, you can do an experiment first. Create a new React project through CRA. In the project, edit the following code:
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;
The page looks like this:
The above React Logo is bound to a click event. Now you need to implement this click event. After clicking the Logo, perform a setState
operation, print a log when the set operation is completed, and add a macro task and a micro task before the set operation. Task. code show as below:
handleClick = () => {
const fans = Math.floor(Math.random() * 10)
setTimeout(() => {
console.log('宏任务触发')
})
Promise.resolve().then(() => {
console.log('微任务触发')
})
this.setState({
count: this.state.count + fans
}, () => {
console.log('新增粉丝数:', fans)
})
}
Obviously, after clicking the Logo, the setState
operation is completed first, and then the micro task trigger and the macro task trigger. Therefore, setState
is earlier than that of microtasks and macrotasks. Even so, it can only be said that its execution timing is earlier than Promise.then
, which does not prove that it is a synchronous task.
handleClick = () => {
const fans = Math.floor(Math.random() * 10)
console.log('开始运行')
this.setState({
count: this.state.count + fans
}, () => {
console.log('新增粉丝数:', fans)
})
console.log('结束运行')
}
Looking at it this way, it seems that setState
is another asynchronous operation. The main reason is that in the life cycle of React and the bound event stream, all setState
setState
queue will be taken out after the entire event ends or after the mount process ends. Calculate and trigger state update. As long as we jump out of React's event flow or life cycle, we can break React's control setState
The easiest way is to setState
into setTimeout
anonymous function.
handleClick = () => {
setTimeout(() => {
const fans = Math.floor(Math.random() * 10)
console.log('开始运行')
this.setState({
count: this.state.count + fans
}, () => {
console.log('新增粉丝数:', fans)
})
console.log('结束运行')
})
}
It can be seen that setState
is essentially in an event loop, and does not switch to another macro task or micro task. It is implemented based on synchronous code in operation, but it looks like asynchronous in behavior. Therefore, there is no question of the interviewer.
How does React control setState?
In the previous case, setState
only setTimeout
. How is this done?
handleClick = () => {
// 正常的操作
this.setState({
count: this.state.count + 1
})
}
handleClick = () => {
// 脱离 React 控制的操作
setTimeout(() => {
this.setState({
count: this.state.count + fans
})
})
}
First review the previous code. In these two operations, we record the call stack in Performance separately to see the difference between the call stacks of the two.
In the call stack, you can see Component.setState
method eventually calls enqueueSetState
method, and enqueueSetState
internal method calls scheduleUpdateOnFiber
method, the difference is that when the normal call, scheduleUpdateOnFiber
in the method is only called ensureRootIsScheduled
, after the incident method, will be called flushSyncCallbackQueue
method. When leaving the React event stream, scheduleUpdateOnFiber
will directly call the flushSyncCallbackQueue
ensureRootIsScheduled
call ends. This method is used to update the state and re-render.
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
if (lane === SyncLane) {
// 同步操作
ensureRootIsScheduled(root, eventTime);
// 判断当前是否还在 React 事件流中
// 如果不在,直接调用 flushSyncCallbackQueue 更新
if (executionContext === NoContext) {
flushSyncCallbackQueue();
}
} else {
// 异步操作
}
}
The above code can simply describe this process, mainly to determine whether executionContext
is equal to NoContext
to determine whether the current update process is in the React event stream.
As we all know, when React binds events, it will synthesize the events and bind them to document
( react@17
has been changed to render
), and finally dispatched by React.
When all events are triggered, batchedEventUpdates$1
executionContext
will be modified here. React knows that setState
under its control at this time.
// executionContext 的默认状态
var executionContext = NoContext;
function batchedEventUpdates$1(fn, a) {
var prevExecutionContext = executionContext;
executionContext |= EventContext; // 修改状态
try {
return fn(a);
} finally {
executionContext = prevExecutionContext;
// 调用结束后,调用 flushSyncCallbackQueue
if (executionContext === NoContext) {
flushSyncCallbackQueue();
}
}
}
Therefore, whether it is to call flushSyncCallbackQueue
directly or postpone the call, it is essentially synchronous here, but there is a question of order.
There will be asynchronous setState in the future
If you look at the above code carefully, you will find that in the scheduleUpdateOnFiber
method, it will be judged whether lane
is synchronous, so is there an asynchronous situation?
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
if (lane === SyncLane) {
// 同步操作
ensureRootIsScheduled(root, eventTime);
// 判断当前是否还在 React 事件流中
// 如果不在,直接调用 flushSyncCallbackQueue 更新
if (executionContext === NoContext) {
flushSyncCallbackQueue();
}
} else {
// 异步操作
}
}
When React upgraded the fiber architecture two years ago, it was preparing for its asynchronousization. React 18 will be officially released in Concurrent
mode, on Concurrent
mode, the official described below.
What is Concurrent mode?
Concurrent mode is a set of new features of React that can help the application stay responsive and make appropriate adjustments based on the user's device performance and network speed. In Concurrent mode, rendering is not blocking. It is interruptible. This improves the user experience. It also unlocks new features that were not possible before.
Now if you want to use the Concurrent
mode, you need to use the experimental version of React. If you are interested in this part, you can read my previous article: "The Evolution of React Architecture-From Synchronous to Asynchronous" .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。