Является ли setState макрозадачей или микрозадачей в React?

интервью внешний интерфейс React.js

Это второй день моего участия в августовском испытании обновлений, подробности о мероприятии:Испытание августовского обновления

Недавно у друга было интервью, и интервьюер задал странный вопрос, который я написал в заголовке.

Чтобы иметь возможность задать этот вопрос, интервьюер не должен очень хорошо знать React. Также возможно, что в резюме интервьюируемого написано, что он знаком с React. Интервьюер хочет использовать этот вопрос, чтобы судить, действительно ли интервьюируемый знаком с React. Реагируйте 🤣.

Правилен ли вопрос интервьюера?

Вопрос интервьюера:setStateЯвляется ли это макрозадачей или микрозадачей, то в его познанииsetStateОпределенно асинхронная операция. судитьsetStateНезависимо от того, является ли это асинхронной операцией или нет, вы можете сначала провести эксперимент, создать новый проект React через CRA и отредактировать в проекте следующий код:

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;

Страница выглядит так:

Приведенный выше логотип React привязан к событию клика, и теперь нам нужно реализовать это событие клика После нажатия на логотип нам нужно сделать это один раз.setStateоперации, распечатайте журнал, когда заданная операция завершена, а перед заданной операцией добавьте макрозадачу и микрозадачу соответственно. код показывает, как показано ниже:

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)
  })
}

Очевидно, что после нажатия на Логотип первый завершенныйsetStateоперации, а затем запуск микрозадач и запуск макрозадач. так,setStateВремя выполнения раньше, чем микрозадача и макрозадача, даже в этом случае можно только сказать, что время его выполнения раньше, чемPromise.then, еще не доказывает, что это синхронная задача.

handleClick = () => {
  const fans = Math.floor(Math.random() * 10)
  console.log('开始运行')
  this.setState({
    count: this.state.count + fans
  }, () => {
    console.log('新增粉丝数:', fans)
  })
  console.log('结束运行')
}

Глядя на это таким образом, кажетсяsetStateЕще одна асинхронная операция. Основная причина в том, что в жизненном цикле React и связанном потоке событий всеsetStateОперация сначала будет кэширована в очереди, а ранее кэшированная будет извлечена после завершения всего события или завершения процесса монтирования.setStateОчередь выполняет расчет, запуская обновление состояния. Пока мы выпрыгиваем из потока событий или жизненного цикла React, мы можем разорвать пары React.setStateконтроль. Самый простой способ - поставитьsetStateставитьsetTimeoutв анонимной функции.

handleClick = () => {
  setTimeout(() => {
    const fans = Math.floor(Math.random() * 10)
    console.log('开始运行')
    this.setState({
      count: this.state.count + fans
    }, () => {
      console.log('新增粉丝数:', fans)
    })
    console.log('结束运行')
  })
}

Отсюда видно, чтоsetStateПо сути, он все еще находится в цикле событий, и он не переключается на другую макрозадачу или микрозадачу, он реализован на основе синхронного кода в работе, но по поведению выглядит асинхронным. Так что к интервьюеру вообще нет вопросов.

Как React контролирует setState?

В предыдущем случаеsetStateтолько вsetTimeoutстанет похоже на синхронный метод, как это делается?

handleClick = () => {
  // 正常的操作
  this.setState({
    count: this.state.count + 1
  })
}
handleClick = () => {
  // 脱离 React 控制的操作
  setTimeout(() => {
    this.setState({
      count: this.state.count + fans
    })
  })
}

Давайте сначала рассмотрим предыдущий код.В этих двух операциях мы записываем стек вызовов в Performance и видим, в чем разница между двумя стеками вызовов.

正常操作

脱离 React 控制的操作

В стеке вызовов вы можете увидетьComponent.setStateметод в конечном итоге будет вызванenqueueSetStateметод, при этомenqueueSetStateметод будет вызываться внутриscheduleUpdateOnFiberметод, разница в том, что при обычном вызовеscheduleUpdateOnFiberметод будет вызывать толькоensureRootIsScheduled, будет вызываться после завершения метода событияflushSyncCallbackQueueметод. Покидая поток событий React,scheduleUpdateOnFiberсуществуетensureRootIsScheduledПосле завершения вызова он будет вызван напрямуюflushSyncCallbackQueueметод, этот метод используется для обновления состояния и повторного рендеринга.

function scheduleUpdateOnFiber(fiber, lane, eventTime) {
  if (lane === SyncLane) {
    // 同步操作
    ensureRootIsScheduled(root, eventTime);
    // 判断当前是否还在 React 事件流中
    // 如果不在,直接调用 flushSyncCallbackQueue 更新
    if (executionContext === NoContext) {
      flushSyncCallbackQueue();
    }
  } else {
    // 异步操作
  }
}

Вышеприведенный код может кратко описать этот процесс, в основном, чтобы судитьexecutionContextРавен ли онNoContextчтобы определить, находится ли текущий процесс обновления в потоке событий React.

Как мы все знаем, когда React привязывает событие, он синтезирует событие и привязывает его кdocumentначальство(react@17Изменено для привязки событий кrenderЭлемент DOM, указанный в то время), наконец отправляется React.

Когда все события сработают, они будут вызываться первымиbatchedEventUpdates$1Этот метод будет изменен здесьexecutionContextзначение, React знаетsetStateв их собственном контроле.

// 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();
    }
  }
}

Итак, будь то прямой вызовflushSyncCallbackQueue, или отложить вызов, здесь все синхронно по своей природе, но есть проблема последовательности.

В будущем будет асинхронный setState

Если вы внимательно посмотрите на приведенный выше код, вы обнаружите, что вscheduleUpdateOnFiberметод, будем судитьlaneЭто синхронно, тогда есть ли асинхронная ситуация?

function scheduleUpdateOnFiber(fiber, lane, eventTime) {
  if (lane === SyncLane) {
    // 同步操作
    ensureRootIsScheduled(root, eventTime);
    // 判断当前是否还在 React 事件流中
    // 如果不在,直接调用 flushSyncCallbackQueue 更新
    if (executionContext === NoContext) {
      flushSyncCallbackQueue();
    }
  } else {
    // 异步操作
  }
}

Когда два года назад React обновил свою волоконную архитектуру, он готовился к асинхронности. Он будет официально выпущен в React 18Concurrentрежим, оConcurrentрежим, официальное введение выглядит следующим образом.

Что такое параллельный режим?

Параллельный режим — это набор новых функций React, которые помогают приложениям оставаться отзывчивыми и соответствующим образом настраиваются в зависимости от производительности устройства пользователя и скорости Интернета. В параллельном режиме рендеринг не блокируется. Его можно прервать. Это улучшает пользовательский опыт. Он одновременно открывает новые возможности, которые раньше были невозможны.

Теперь, если вы хотите использоватьConcurrentрежим требует использования экспериментальной версии React. Если вам интересна эта часть, вы можете прочитать мою предыдущую статью:Эволюция архитектуры React — от синхронной к асинхронной.

公众号推广.png