7

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.

正常操作

脱离 React 控制的操作

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" .


Shenfq
4k 声望6.8k 粉丝