Подробный механизм setState реакции

React.js

Исходный код реакции, изучаемый в этой статье, имеет версию v0.8.0. Начиная с этой версии, по траектории изменений версий мы можем видеть историю эволюции механизма setState.

Введение в историю эволюции setState

Что касается механизма setState реагирования, я считаю, что есть два хорошо известных термина: «пакетное обновление» и «асинхронное выполнение». На самом деле оба термина описывают одно и то же. Почему ты это сказал? Потому что люди, которые углубились в исходный код, знают, что«Пакетное обновление» — причина, «асинхронное выполнение» — следствие. Мир любит смотреть на внешний вид и результаты, поэтому обычно я использую «асинхронное выполнение» или «синхронное выполнение», чтобы делать соответствующие объяснения.

Давненько я не использовал react для разработки, я смутно помню, что в очень ранней версии выполнение setState вначале было синхронным, а потом оно было изменено на асинхронное выполнение в определенных сценариях и синхронное выполнение в определенных сценариях. Наконец, по настоящее время все это выполняется асинхронно. Чтобы добавить: после проверкиchange log, React изменил setState на асинхронное выполнение в версии v0.13.0. Задокументировано в качестве доказательства:

Calls to setState in life-cycle methods are now always batched and therefore asynchronous. Previously the first call on the first mount was synchronous.

В версии v0.8.0 setState в прослушивателе событий выполняется асинхронно, а в прослушивателе событийfirst call on the first mount, то есть функция жизненного цикла componentDidMount выполняется синхронно). Не верю? Мы используем reactv0.8.0 для проверки следующего:

import React from 'react';

const Count  = React.createClass({
  getInitialState() {
        return {
            count: 0
        }
   },

  render() {
    return <button onClick={()=> {
      this.setState({count: this.state.count + 1});
      console.log(this.state.count);
      this.setState({count: this.state.count + 1});
      console.log(this.state.count);
      this.setState({count: this.state.count + 1});
      console.log(this.state.count);
      }}>{this.state.count}</button>
  }

  componentDidMount() {
    this.setState({count: this.state.count + 1});
    console.log(this.state.count);
    this.setState({count: this.state.count + 1});
    console.log(this.state.count);
    this.setState({count: this.state.count + 1});
    console.log(this.state.count);
  }
})

export default Count; 

После начального монтирования результат журнала в componentDidMount выглядит следующим образом:

1
2
3

В этот момент число на кнопке равно 3. Это доказывает, что в функции жизненного цикла componentDidMount выполнение setState является синхронным.

Если мы нажмем кнопку «Далее», номер кнопки просто увеличится на 1 (вместо 3) и станет равным 4. Взгляните на журнал, и результат:

3
3
3

Вполне возможно, что вызов setState в прослушивателе событий click является асинхронным, поэтому мы не можем сразу получить последнее значение состояния. Если мы хотим получить последнее значение состояния:

  • Либо получить его в обратном вызове метода setState;
  • Либо получите его в функции жизненного цикла componentDidUpdate;
  • Либо получить его в конце цикла событий. Например, в прослушивателе событий используйте setTimeout получить:
setTimeout(() => {
    console.log('into setTimeout',this.state.count);
}, 0);

Тот же пример кода, что получится, если мы запустим reactv16.8.6? :

import React from 'react';

class Count extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
  }

  render() {
    return <button onClick={()=> {
      this.setState({count: this.state.count + 1});
      console.log(this.state.count);
      this.setState({count: this.state.count + 1});
      console.log(this.state.count);
      this.setState({count: this.state.count + 1});
      console.log(this.state.count);
      }}>{this.state.count}</button>
  }

  componentDidMount() {
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
  }
}

export default Count; 

В результате после выполнения componentDidMount число на кнопке равно 1, а консоль выводит:

0
0
0

Затем нажмите кнопку, номер кнопки увеличится на 1 на исходной основе и станет равным 2, и консоль выведет:

1
1
1

То есть в версии reactv16.8.6 setState выполняется асинхронно. На самом деле это утверждение не очень строгое. Потому что, как мы сказали выше,Асинхронное выполнение — это только результат, потому что это пакетное обновление. В реакции, если setState должен выполняться асинхронно, он должен быть в настоящее время в транзакции «пакетного обновления», и запись вызовов setState в асинхронном коде javascript (например, setTimeout, обещание и т. д.) может избежать реакции Эта транзакция «пакетного обновления» , в это время setState выполняется синхронно. В прослушивателе событий из приведенной выше версии reactv16.8.6 давайте попробуем обернуть несколько вызовов setState с помощью setTimeout?

import React from 'react';

class Count extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
  }

  render() {
    return <button onClick={()=> {
      setTimeout(()=> {
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
    }, 0);
      }}>{this.state.count}</button>
  }

  componentDidMount() {
    setTimeout(()=> {
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
    }, 0);
  }
}

export default Count; 

Та же операция, каков результат? В результате номер кнопки становится равным 6, а распечатка выглядит следующим образом:

1
2
3
4
5
6

То есть setState, обернутый setTimeout, выполняется синхронно. Поэтому утверждение «setState выполняется асинхронно после версии reactv0.13.0» недостаточно строго, можно лишь сказать, что в нормальных условиях он выполняется асинхронно.

Принцип пакетного обновления

Хорошо, приступим к делу. После краткого представления истории изменений механизма setState давайте формально рассмотрим реализацию механизма setState в reactv0.8.0.

Для начала посмотрим, как реализован метод setState (в ReactCompositeComponent.js):

/**
   * Sets a subset of the state. Always use this or `replaceState` to mutate
   * state. You should treat `this.state` as immutable.
   *
   * There is no guarantee that `this.state` will be immediately updated, so
   * accessing `this.state` after calling this method may return the old value.
   *
   * There is no guarantee that calls to `setState` will run synchronously,
   * as they may eventually be batched together.  You can provide an optional
   * callback that will be executed when the call to setState is actually
   * completed.
   *
   * @param {object} partialState Next partial state to be merged with state.
   * @param {?function} callback Called after state is updated.
   * @final
   * @protected
   */
  setState: function(partialState, callback) {
    // Merge with `_pendingState` if it exists, otherwise with existing state.
    this.replaceState(
      merge(this._pendingState || this.state, partialState),
      callback
    );
  }

Реализация метода setState очень проста и не требует воображаемого высокоуровневого кода. Сначала он выполняет неглубокое слияние входящего частичного состояния с обрабатываемым состоянием (_pendingState) или текущим состоянием (неглубокие слияния, то есть охват самого внешнего ключа объекта). Затем передайте его методу replaceState. Реализация метода replaceState выглядит следующим образом:

 /**
   * Replaces all of the state. Always use this or `setState` to mutate state.
   * You should treat `this.state` as immutable.
   *
   * There is no guarantee that `this.state` will be immediately updated, so
   * accessing `this.state` after calling this method may return the old value.
   *
   * @param {object} completeState Next state.
   * @param {?function} callback Called after state is updated.
   * @final
   * @protected
   */
  replaceState: function(completeState, callback) {
    validateLifeCycleOnReplaceState(this);
    this._pendingState = completeState;
    ReactUpdates.enqueueUpdate(this, callback);
  },

В методе replaceState первым шагом является проверка LifeCycleState и выдача некоторых предупреждений.Эта функция в порядке, поэтому ее нет в списке;второй шаг — изменение объекта, полученного неглубоким слиянием метода setState в состояние ожидания (_pendingState ) ; И третий шаг — это ключ для открытия двери механизма setState, то есть этот код:

ReactUpdates.enqueueUpdate(this, callback);

«enqueueUpdate» можно перевести как «поместить в очередь, дождаться обновления». Эта очередь на самом делеdirtyComponents, что мы сразу видим в коде реализации метода enqueueUpdate. Код реализации метода enqueueUpdate выглядит следующим образом (в ReactUpdates.js):

/**
 * Mark a component as needing a rerender, adding an optional callback to a
 * list of functions which will be executed once the rerender occurs.
 */
function enqueueUpdate(component, callback) {
  ("production" !== process.env.NODE_ENV ? invariant(
    !callback || typeof callback === "function",
    'enqueueUpdate(...): You called `setProps`, `replaceProps`, ' +
    '`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
    'is not callable.'
  ) : invariant(!callback || typeof callback === "function"));

  ensureBatchingStrategy(); 

  if (!batchingStrategy.isBatchingUpdates) {
    component.performUpdateIfNecessary();
    callback && callback();
    return;
  }

  dirtyComponents.push(component);

  if (callback) {
    if (component._pendingCallbacks) {
      component._pendingCallbacks.push(callback);
    } else {
      component._pendingCallbacks = [callback];
    }
  }
}

Сначала напомните, что должен быть введен «объект политики пакетного обновления», иначе будет выдано предупреждение. В React есть реализация объекта стратегии пакетного обновления по умолчанию, код реализации находится в файле ReactDefaultBatchingStrategy.js, который выглядит так:

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  /**
   * Call the provided function in a context within which calls to `setState`
   * and friends are batched such that components are not updated unnecessarily.
   */
  batchedUpdates: function(callback, param) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    // The code is written this way to avoid extra allocations
    if (alreadyBatchingUpdates) { // alreadyBatchingUpdates的值什么情况为true,这是一个值得研究的问题
      callback(param);
    } else {
      transaction.perform(callback, null, param);
    }
  }
};

А где укол делают? Внедрение зависимостей в реакцию выполняется в ReactDefaultInjection.js. Этот файл у нас есть«Углубленная система синтетических событий React»также упоминается в. Здесь я просто извлекаю код, относящийся к теме этой статьи:

 ReactUpdates.injection.injectBatchingStrategy(
    ReactDefaultBatchingStrategy
  );

Теперь вернемся к обсуждению исходного кода метода enqueueUpdate. Как видно из исходного кода, если флаг isBatchingUpdates объекта batchingStrategy равен false, то мы напрямую входим в процесс обновления интерфейса:

if (!batchingStrategy.isBatchingUpdates) {
    component.performUpdateIfNecessary();
    callback && callback();
    return;
  }

В противном случае react помещает экземпляр компонента, который в данный момент вызывает метод setState, в dirtyComponents и сохраняет функцию обратного вызова метода setState в поле _pendingCallbacks экземпляра компонента:

dirtyComponents.push(component);

  if (callback) {
    if (component._pendingCallbacks) {
      component._pendingCallbacks.push(callback);
    } else {
      component._pendingCallbacks = [callback];
    }
  }

На самом деле, если мы напишем код в виде if...else..., мы сможем лучше увидеть направление выполнения кода:

if (!batchingStrategy.isBatchingUpdates) {
    component.performUpdateIfNecessary();
    callback && callback();
} else {
    dirtyComponents.push(component);

    if (callback) {
        if (component._pendingCallbacks) {
          component._pendingCallbacks.push(callback);
        } else {
          component._pendingCallbacks = [callback];
        }
    }
}

Прежде чем идти дальше, позвольте мне обсудить, что такое dirtyComponents.

Во-первых, из структуры данных это массив:

var dirtyComponents = [];

Во-вторых, нам нужно выяснить, что именно означает «грязные компоненты».

/**
 * Mark a component as needing a rerender, adding an optional callback to a
 * list of functions which will be executed once the rerender occurs.
 */

Объединив комментарии, приведенные в приведенном выше исходном коде, и собственное исследование, я могу дать определение «грязным компонентам»:

Компонент считается «грязным», если его повторный рендеринг не может быть выполнен синхронно.

Если мы сможем понять название метода "setState" под другим углом, тогда концепция "грязных компонентов" может быть лучше понята. На самом деле, насколько я понимаю, "setState" не должен называться setState, а должен называться "requestToSetState". То есть, когда пользователь вызывает метод setState, суть в том, чтобы запросить реакцию, которая поможет нам немедленно обновить интерфейс. Семантическое понимание setState заключается в запросе обновления.По сути, он также делает одну вещь, то есть неглубоко объединяет входящее частичное состояние с исходным состоянием и, наконец, присваивает его _pendingState экземпляра компонента. Потому что react не сразу ответит на наш запрос на обновление, а в это время компонент находится в состоянии блокировки "_pendingState". В этом случае иностранцы называют его «грязным». Мы, китайцы, тоже должны понимать.

Итак, вопрос, почему не реагирует сразу на наш запрос обновления? Собственно, в приведенном выше исходном коде метода enqueueUpdate мы можем найти ответ на вопрос. То есть проверить, установлен ли флаг пакетного обновления (isBatchingUpdates).

«isBatchingUpdates», буквальное значение очень прямолинейно: «Независимо от того, находится ли оно в пакетном обновлении», поэтому я не буду здесь говорить больше. Далее мы продолжаем спрашивать себя: «Когда значение isBatchingUpdates истинно?»

Мы могли бы также выполнить глобальный поиск После долгих манипуляций мы обнаружим, что все операторы, назначенные «isBatchingUpdates», находятся в файле ReactDefaultBatchingStrategy.js:

Первое место это:

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,
  // ......
};

Второе место также находится в этом объекте ReactDefaultBatchingStrategy, в частности, в его определении метода batchedUpdates:

  batchedUpdates: function(callback, param) {
    // ......
    ReactDefaultBatchingStrategy.isBatchingUpdates = true; 
    // ......
  }
};

Третье место — в методе close обёртки RESET_BATCHED_UPDATES ReactDefaultBatchingStrategyTransaction:

var RESET_BATCHED_UPDATES = {
  // ......
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false; 
  }
};

Сочетая приведенные выше результаты поиска и понимание режима транзакций, нетрудно увидеть, что процесс присвоения «isBatchingUpdates» выглядит следующим образом:

  1. При инициализации объекта ReactDefaultBatchingStrategy присвойте значение по умолчанию false;
  2. Когда третья сторона вызывает метод batchedUpdates, значение устанавливается равным true;
  3. После завершения ReactDefaultBatchingStrategyTransaction значение равно false;

Так кто и где вызывает метод batchedUpdates? После глобального поиска мы обнаружили, что существует всего два вызова метода batchedUpdates:

// 在ReactUpdates.js里面
function batchedUpdates(callback, param) {
  ensureBatchingStrategy();
  batchingStrategy.batchedUpdates(callback, param);
}
// 在ReactUpdates.js里面
 handleTopLevel: function(
      topLevelType,
      topLevelTarget,
      topLevelTargetID,
      nativeEvent) {

    var events = EventPluginHub.extractEvents(
      topLevelType,
      topLevelTarget,
      topLevelTargetID,
      nativeEvent
    );

    ReactUpdates.batchedUpdates(runEventQueueInBatch, events);
  }

После трассировки кода мы обнаружили, что вызов ReactUpdates.batchedUpdates() в методе handleTopLevel является настоящей записью вызова. Поскольку фактический процесс передачи ссылки выглядит так («A -> B» означает «A передает свою собственную ссылку B»):

ReactDefaultBatchingStrategy.batchedUpdates -> batchingStrategy.batchedUpdates  ->  
ReactUpdates.batchedUpdates

Передача batchingStrategy и ReactDefaultBatchingStrategy осуществляется посредством вышеупомянутой инъекции зависимостей:

 ReactUpdates.injection.injectBatchingStrategy(
    ReactDefaultBatchingStrategy
  );

Метод handleTopLevel — наш старый друг. я здесь«Углубленная система синтетических событий React»Он также упоминается в . Читатели, читавшие эту статью, возможно, знают, что существует такая цепочка вызовов: topLevelCallback() -> handleTopLevel() -> обработчик событий(); и наш вызов setState выполняется в обработчике событий. Сегодня полная цепочка вызовов должна выглядеть так («A -> B» означает «A вызывает B»):

topLevelCallback() ->  
handleTopLevel() -> 
ReactUpdates.batchedUpdates() -> 
runEventQueueInBatch()  -> 
event listener()  -> 
setState()

На данный момент, я думаю, вы также понимаете, почему вызов setState в прослушивателе событий выполняется асинхронно.Это связано с тем, что ReactUpdates.batchedUpdates() выполняется до выполнения setState, а вызов ReactUpdates.batchedUpdates() включает режим пакетного обновления..

если мы предположимcomponent.performUpdateIfNecessary();После звонка следующим шагом является процесс обновления интерфейса. Итак, когда компоненты, помещенные в массив dirtyComponents, войдут в этот процесс? В исходном коде ответ на этот вопрос все еще несколько скрыт. Если у вас есть более глубокое понимание транзакций, вы можете найти ответ быстрее. Потому что ответ находится в транзакции, точнее в транзакции под названием ReactDefaultBatchingStrategyTransaction. Итак, позвольте мне рассказать вам, как я нашел его.

Ссылка на ReactUpdates.batchedUpdates указывает на ReactDefaultBatchingStrategy.batchedUpdates, что было объяснено выше. Код реализации ReactDefaultBatchingStrategy.batchedUpdates заслуживает рассмотрения:

batchedUpdates: function batchedUpdates(callback, param) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
    
    ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    
    // The code is written this way to avoid extra allocations
    if (alreadyBatchingUpdates) {
      callback(param);
    } else {
      transaction.perform(callback, null, param);
    }
}

Поскольку срабатывание события одного типа приведет только к вызову метода ReactUpdates.batchedUpdates, а начальное значение ReactDefaultBatchingStrategy.isBatchingUpdates равно false (то есть первое прочитанное значение ужеBatchingUpdates равно false), поэтому в одном «событии loop", оператор условного перехода if в методе batchedUpdates вообще не будет выполняться. В приведенном выше комментарии также упоминается причина этого: «Код написан таким образом, чтобы избежать лишних выделений памяти». Итак, при вызове метода batchedUpdates нам нужно обратить внимание только на это утверждение:

transaction.perform(callback, null, param);

В этот момент мы видим транзакцию. Глядя на исходный код ReactDefaultBatchingStrategy.js, мы узнаем, что эта транзакцияReactDefaultBatchingStrategyTransaction. То есть наш прослушиватель событий выполняется в этой транзакции ReactDefaultBatchingStrategyTransaction. Поскольку вызов setState находится в прослушивателе событий, вызов setState также находится в этой транзакции. Режим транзакций, используемый в реакции, похож на режим сэндвич-бисквитов: основные методы «обернуты» обертками, верхний уровень — это метод инициализации, а нижний уровень — метод закрытия. Модель, когда она работает, вероятно, выглядит так:

 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+

Затем, в зависимости от ReactDefaultBatchingStrategyTransaction, его модель времени выполнения указывается следующим образом.

--------FLUSH_BATCHED_UPDATES.emptyFunction-------
|
|  -----RESET_BATCHED_UPDATES.emptyFunction------
|  |
|  |    runEventQueueInBatch
|  |
--------FLUSH_BATCHED_UPDATES.close------ // 这个close方法是指向ReactUpdates.flushBatchedUpdates方法
   |    
   -------FLUSH_BATCHED_UPDATES.close-------
        // 上面这个close方法是指向以下函数:
        function() {
            ReactDefaultBatchingStrategy.isBatchingUpdates = false; // 事务完成后,把批量更新开关重置为false
          }

Вышеупомянутая модель выполняется сверху вниз. После выполнения метода runEventQueueInBatch выполняется вызов setState. За ним следует метод close обёртки FLUSH_BATCHED_UPDATES: ReactUpdates.flushBatchedUpdates. И именно в этом методе мы ищем ответы. Итак, давайте взглянем на конкретную реализацию метода flushBatchedUpdates этого модуля ReactUpdates:

// 在我们的老朋友ReactUpdates.js文件中
function flushBatchedUpdates() {
  // Run these in separate functions so the JIT can optimize
  try {
    runBatchedUpdates();
  } catch (e) {
    // IE 8 requires catch to use finally.
    throw e;
  } finally {
    clearDirtyComponents();
  }
}

И какая конкретная реализация метода runBatchedUpdates:

function runBatchedUpdates() {
  // Since reconciling a component higher in the owner hierarchy usually (not
  // always -- see shouldComponentUpdate()) will reconcile children, reconcile
  // them before their children by sorting the array.

  dirtyComponents.sort(mountDepthComparator);

  for (var i = 0; i < dirtyComponents.length; i++) {
    // If a component is unmounted before pending changes apply, ignore them
    // TODO: Queue unmounts in the same list to avoid this happening at all
    var component = dirtyComponents[i];
    if (component.isMounted()) {
      // If performUpdateIfNecessary happens to enqueue any new updates, we
      // should not execute the callbacks until the next render happens, so
      // stash the callbacks first
      var callbacks = component._pendingCallbacks;
      component._pendingCallbacks = null;
      component.performUpdateIfNecessary();
      if (callbacks) {
        for (var j = 0; j < callbacks.length; j++) {
          callbacks[j].call(component);
        }
      }
    }
  }
}

Вы видели людей с глазами из 24-каратного титанового сплава? Мы увидели знакомую фигуру:dirtyComponentsа такжеcomponent.performUpdateIfNecessary();. Да, на данный момент мы можем ответить на вопросы, которые задавали ранее.

Первый вопросительный пункт: "Кто?" Ответ: "это метод runBatchedUpdates".

Второй вопрос: "Когда?" Ответ: «Когда ReactDefaultBatchingStrategyTransaction подходит к концу, выполните行FLUSH_BATCHED_UPDATESПри закрытии метода обертки".

На этом этапе, независимо от того, выполняется ли setState синхронно или асинхронно, они достигнут точки встречи во время своего выполнения. Это место встречи:

component.performUpdateIfNecessary();

Из названия метода, как следует из названия, процесс, который начинается с этого пересечения, должен быть процессом обновления интерфейса. О механизме обновления интерфейса будет рассказано в другой статье, поэтому я не буду его здесь подробно описывать.

четыре предмета

Глядя вперед с этого перекрестка, мы вроде как поняли это».Почему setState помещается в прослушиватель событий и почему setState, помещенный в componentDidMount, выполняется синхронно?«Вот в чем проблема. Итак, оглядываясь назад с этого перекрестка, в чем наша проблема? Ответ в том, что наша проблема в том»Что именно происходит, когда setState вызывается несколько раз?". Давайте исследуем его.

Множественные вызовы setState можно разделить на два сценария и два режима для изучения. Таким образом, у нас будут следующие четыре темы исследования:

  1. В режиме пакетного обновления вызовите setState несколько раз подряд в одном и том же методе
  2. В режиме пакетного обновления в процессе обновления дерева компонентов компоненты на разных уровнях вызывают setState
  3. В режиме непакетного обновления вызовите setState несколько раз подряд в одном и том же методе.
  4. В режиме непакетного обновления в процессе обновления дерева компонентов компоненты на разных уровнях вызывают setState

Ниже каждая тема изучается по отдельности с конкретным примером кода.

Тема 1

const Parent = React.createClass({
    getInitialState(){
        return {
            count: 0
        }
    },
    handleClick(){
        this.setState({
            count: this.state.count +  1
        });
        this.setState({
            count: this.state.count +  1
        });
        this.setState({
            count: this.state.count +  1
        });
    },
    render(){
        return react.DOM.button({
            onClick: this.handleClick
        },`I have been clicked ${this.state.count} times`)
    }
})

Несколько вызовов setState в методе handleClick выше выполняются асинхронно, поскольку они находятся в режиме пакетного обновления. Эта причинно-следственная связь была четко описана в статье, поэтому я повторю ее здесь. Здесь мы все пытаемся выяснить, что происходит после нескольких вызовов setState.

В режиме пакетного обновления также кратко упоминаются события, происходящие после нескольких вызовов setState. То есть сначала выполните поверхностное слияние объектов состояния, обновите результат слияния до свойства _pendingState экземпляра компонента, а затем поместите текущий экземпляр компонента в dirtyComponents. Для этого примера, потому что триthis.state.countЗначение доступа 0, три{count: this.state.count + 1}Результат поверхностного слияния:

{
    count: 1
}

После этого экземпляр родительского компонента помещается в dirtyComponents. Поскольку после вызова setState он передается один раз, поэтому в компоненте dirtyComponents должно быть три идентичных экземпляра компонента. С таким же успехом мы могли бы распечатать dirtyComponents, чтобы увидеть:

В reactv0.8.0 экземпляр компонента выглядит так:

(Смотрите, _pendingState{count: 1}ждем нас) Ну вот и грязные компоненты готовы. Он будет очищен, когда ReactDefaultBatchingStrategyTransaction выполнит метод закрытия. Метод, отвечающий за промывку, упомянут выше:

function flushBatchedUpdates() {
  // Run these in separate functions so the JIT can optimize
  try {
    runBatchedUpdates();
  } catch (e) {
    // IE 8 requires catch to use finally.
    throw e;
  } finally {
    clearDirtyComponents();
  }
}

Исходный код метода runBatchedUpdates также приведен выше:

function runBatchedUpdates() {
  // Since reconciling a component higher in the owner hierarchy usually (not
  // always -- see shouldComponentUpdate()) will reconcile children, reconcile
  // them before their children by sorting the array.

  dirtyComponents.sort(mountDepthComparator);

  for (var i = 0; i < dirtyComponents.length; i++) {
    // If a component is unmounted before pending changes apply, ignore them
    // TODO: Queue unmounts in the same list to avoid this happening at all
    
    var component = dirtyComponents[i];
    if (component.isMounted()) {
      // If performUpdateIfNecessary happens to enqueue any new updates, we
      // should not execute the callbacks until the next render happens, so
      // stash the callbacks first
      
      var callbacks = component._pendingCallbacks;
      component._pendingCallbacks = null;
      component.performUpdateIfNecessary();
      if (callbacks) {
        for (var j = 0; j < callbacks.length; j++) {
          callbacks[j].call(component);
        }
      }
    }
  }
}

В этой теме мы не будемdirtyComponents.sort(mountDepthComparator);, потому что это относится к проблеме, которая будет обсуждаться в соседней теме. Как видно из исходного кода, следующее, что нужно сделать, — пройтись по массиву dirtyComponents и вызвать их методы PerformUpdateIfNecessary один за другим.

В React согласование — это непрерывный процесс, который начинается с компонента верхнего уровня и возвращается к компоненту нижнего уровня. Я называю этот процесс обновления «однократным обновлением».

«Одноразовое обновление» можно условно разделить на четыре этапа:

  1. запросить обновление
  2. принять решение о согласии на просьбу
  3. Рекурсивный обход от родительского компонента к дочернему
  4. реальная манипуляция DOM

По «IfNecessary» в названии метода PerformUpdateIfNecessary можно догадаться, что этот метод относится ко второму этапу. Итак, как именно реакция решает, соглашаться ли на запрос на обновление?

Мы можем найти ответ по стеку вызовов, участвующему во втором этапе (порядок вызовов в стеке вызовов снизу вверх):

Уточним контекст вызова из стека вызовов выше (порядок вызова здесь сверху вниз):

ReactCompositeComponent.performUpdateIfNecessary ->
ReactComponent.Mixin.performUpdateIfNecessary ->
ReactCompositeComponent._performUpdateIfNecessary ->
ReactCompositeComponent._performComponentUpdate

Если вы внимательно посмотрите на исходный код этого стека вызовов, вы обнаружите, что есть три основания для принятия решения о согласии на запрос на обновление, которые мы можем понимать как три уровня таможенной очистки.

Первый — в методе ReactCompositeComponent.performUpdateIfNecessary:

// ......
if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
  return;
}
// ......

Второй и третий находятся в методе ReactCompositeComponent._performUpdateIfNecessary:

if (this._pendingProps == null && this._pendingState == null && !this._pendingForceUpdate) {
  return;
}
if (this._pendingForceUpdate || !this.shouldComponentUpdate || this.shouldComponentUpdate(nextProps, nextState)) {
  this._pendingForceUpdate = false;
  // Will set `this.props` and `this.state`.
  this._performComponentUpdate(nextProps, nextState, transaction);
} else {
  // If it is not determined that a component should not update, we still want
  // to set props and state.
  this.props = nextProps;
  this.state = nextState;
}

Теперь мы пройдемся по этим судам один за другим.

для первого звонкаcomponent.performUpdateIfNecessary();, В этом примере, поскольку родительский компонент является компонентом верхнего уровня этого обновления и обновляется путем вызова setState, его значение состояния CompositeLifeCycle не может бытьCompositeLifeCycle.RECEIVING_PROPS. И поскольку это обновление компонента, а не начальное монтирование, значение состояния композита LifeCycleState не может быть MOUNTING. Итак, первый уровень пройден.

Давайте посмотрим на второй уровень. this._pendingProps имеет значение null по той же причине, что и выше. Но this._pendingState не может быть нулевым. Из-за трех последовательных вызовов setState в режиме пакетного обновления суть заключается в неглубоком слиянии объектов, поэтому значение this._pendingState на данный момент равно{count: 1}, значит, второй уровень тоже пройден.

Переходите на третий уровень. Поскольку мы не вызывали метод forceUpdate для обновления интерфейса, значение this._pendingForceUpdate равно false, поскольку мы не монтировали функцию жизненного цикла shouldComponentUpdate,!this.shouldComponentUpdateверно, так что мы снова успешно проходим третий уровень.

После прохождения трех уровней вызовите метод this._performComponentUpdate, чтобы официально войти в последующий процесс обновления интерфейса.

Подводя итог, в этом примере первый вызов setState представляет собой полное «одноразовое обновление».

После того, как дерево компонентов завершило «однократное обновление», мы вызываем второеcomponent.performUpdateIfNecessary(). Здесь стоит отметить, что экземпляр компонента, который трижды отправляет dirtyComponents, на самом деле один и тот же. Не верю? Сделаем лог в методе runBatchedUpdates, чтобы увидеть:

function runBatchedUpdates() {
 // ......
  console.log(dirtyComponents[0] === dirtyComponents[1], dirtyComponents[1] === dirtyComponents[2]);  
 // .....
}

распечатать результат:

true true

Итак, при первом вызове setState после успешного прохождения второго уровня происходит действие, которое очищает _pendingState (в методе ReactCompositeComponent._performUpdateIfNecessary):

_performUpdateIfNecessary: function(transaction) {
   // ......
   var nextState = this._pendingState || this.state;
   this._pendingState = null; 
   // ......
}

И поскольку три экземпляра компонента в dirtyComponents являются одним и тем же референтом, поэтому во втором вызовеcomponent.performUpdateIfNecessary()Второй уровень соответствующего определения зазора пройти невозможно. В коде это,this._pendingProps == null && this._pendingState == null && !this._pendingForceUpdateЗначение true (в основном потому, что значение this._pendingState равно null), поэтому оператор return выполняется, и процесс «обновления в одном зеркале» здесь резко заканчивается.

Аналогично, третийcomponent.performUpdateIfNecessary()Звонок тот же, поэтому я не буду вдаваться в подробности. Давайте напишем лог в методе ReactCompositeComponent._performUpdateIfNecessary, чтобы доказать это:

console.log('into _performUpdateIfNecessary:', this._pendingProps == null && this._pendingState == null && !this._pendingForceUpdate);

Для этого примера кода результат печати:

into _performUpdateIfNecessary: false
into _performUpdateIfNecessary: true
into _performUpdateIfNecessary: true

Подводя итог, можно сказать, что три последовательных вызова setState в обработчике событий — это, по сути, первые три неглубоких слияния объектов и, наконец, одно «одноразовое обновление» дерева компонентов, а последние два — процесс обновления, инициированный setState, прерывается на полпути. и резко остановился. Возможно, это более образное значение «пакетного обновления» в реакции.

Тема 2

const Child = React.createClass({
    getInitialState(){
        return {
            count: 0
        }
    },
    handleClick(){
        this.setState({
            count: this.state.count +  1
        });
    },
    render(){
        return react.DOM.button({
            onClick: this.handleClick
        },`I have been clicked ${this.state.count} times`)
    }
});

const Parent = React.createClass({
    getInitialState(){
        return {
            count: 0
        }
    },
    handleClick(){
        this.setState({
            count: this.state.count +  1
        });
    },
    render(){
        return react.DOM.div({
            onClick: this.handleClick
        },Child())
    }
});

В этом разделе давайте поговорим о ситуации, когда компоненты на разных уровнях несколько раз вызывают setState в одном и том же дереве компонентов.

Мы видим, чтоParentа такжеChildКомпонент имеет обработчик события handleClick. Все они регистрируются в фазе всплытия события. Поэтому, когда происходит событие щелчка пользователя, сначала вызывается Child.handleClick, а потом — Parent.click. А поскольку в данный момент он находится в «режиме пакетного обновления», в результатеChildЭкземпляры компонентов сначала помещаются в dirtyComponents,ParentПосле того, как экземпляр компонента помещен в него:

Основываясь на этом результате, чтобы понять следующий процесс обновления, вы можете подумать, что при сбросе dirtyComponents сначала будет обновлен дочерний компонент, а затем будет обновлен родительский компонент. вообще-то нет. Потому что перед выполнением flush dirtyComponents будет выполняться операция сортировки в соответствии с иерархией компонентов. Это то, что мы упоминали в предыдущем вопросе нашего исследования.dirtyComponents.sort(mountDepthComparator)вопрос:

function runBatchedUpdates() {
  // Since reconciling a component higher in the owner hierarchy usually (not
  // always -- see shouldComponentUpdate()) will reconcile children, reconcile
  // them before their children by sorting the array.

  dirtyComponents.sort(mountDepthComparator);
  // ......
}

Эта строка кода очень важна.Сортируя грязные компоненты, React сохраняет порядок процесса согласования всегда от корневого компонента дерева компонентов до самого нижнего компонента. Стоит упомянуть поле экземпляра компонента "_mountDepth", по которому выполняется сортировка. Значение этого поля — число, указывающее уровень текущего компонента в дереве компонентов. Работа по нумерации начинается с корневого компонента, основывается на 0 и увеличивается слой за слоем. На приведенном выше снимке экрана видно, что значение _mountDepth родительского компонента равно 0, а значение _mountDepth дочернего компонента равно 2.

Вернемся к использованию метода сортировки массивов: По возрастанию: sort((a,b) => a - b); По убыванию: sort((a,b) => b - a)

Реализация функции mountDepthComparator из исходного кода:

function mountDepthComparator(c1, c2) {
  return c1._mountDepth - c2._mountDepth;
}

Мы видим, что react сортирует грязные компоненты в порядке возрастания на основе значения _mountDepth, таким образом гарантируя, что корневой компонент всегда находится на первом месте в массиве:

Другими словами, в «режиме пакетного обновления», независимо от того, в каком порядке компоненты на разных уровнях в дереве компонентов вызывают setState, они в конечном итоге будут переставлены перед сбросом dirtyComponents. Итак, в этом примере реакция сначала попытается обновитьParentкомпоненты, тоChildкомпоненты. В «режиме пакетного обновления» обновления компонентов всегда обновляют состояние всех компонентов (точнее, обновляют _pendingState), а затем пытаются делать реальные обновления уровня DOM. Что касается этого момента, я думаю, что те, кто читал вышеизложенное, вероятно, знают об этом.

В примере реакция сначала попытается обновитьParentкомпоненты. Сравните родительский компонент в предыдущем примере, поскольку все они находятся в «режиме пакетного обновления» и являются корневыми компонентами, поэтому обновление родительского компонента в этом примере также является «однократным обновлением».

потому чтоParent«Единичное обновление» компонента обновит все компоненты всего дерева компонентов.ChildКомпонент не является исключением, и его поле «_pendingState» также очищается. Когда придет твоя очередь смывать себя,ChildКомпонент, поскольку this._pendingState в настоящее время имеет значение null, поэтомуChildПроцесс обновления компонента не может пройти второй уровень.

До сих пор то, что происходило в этом примере при многократном вызове setState, на самом деле то же самое, что и в примере 1, а именно: сначала поверхностное слияние состояния, обновление до _pendingState, а затем «одноразовое обновление». Все последующие процессы обновления будут прекращены на втором уровне.

Далее, мы можем захотеть увеличить сложность исследования. Как увеличить закон? Добавьте компонент функции жизненного цикла componentWillReceiveProps в дочерний компонент:


    const Child = React.createClass({
    getInitialState(){
        return {
            count: 0
        }
    },
    handleChildClick(){
        this.setState({
            count: this.state.count +  2
        });
    },
    componentWillReceiveProps(){
        this.setState({
            count: 10
        });
    },
    render(){
        return React.DOM.button({
            onClick: this.handleChildClick
        },`Child count ${this.state.count} times`)
    }
});

После добавления componentWillReceiveProps возникают три проблемы:

  1. Кто первым вызовет setState для handleChildClick и componentWillReceiveProps?
  2. Каково значение состояния конечного дочернего компонента?
  3. Что именно происходит, когда setState вызывается в componentWillReceiveProps этого примера?

Давайте ответим на них один за другим.

Мы начинаем считать, когда происходит действие щелчка пользователя, и поток выполнения выглядит следующим образом:

  1. Выполните handleChildClick, чтобы обновить _pendingState дочернего компонента;
  2. Выполните handleParentClick, чтобы обновить _pendingState родительского компонента;
  3. Родительский компонент будет обновлять дочерний компонент во время процесса «однократного обновления», и в это время будет вызываться компонент componentWillReceiveProps.
  4. При вызове componentWillReceiveProps() вызовите setState, чтобы снова обновить _pendingState дочернего компонента.

Ответ на вопрос 1:

В приведенном выше процессе сначала выполняется setState handleChildClick, а затем выполняется setState componentWillReceiveProps.

Ответ на вопрос 2:

Из исходного кода реализации ReactCompositeComponent._performUpdateIfNecessary, ответственного за вызов функции жизненного цикла componentWillReceiveProps:

 _performUpdateIfNecessary: function(transaction) {
    if (this._pendingProps == null &&
        this._pendingState == null &&
        !this._pendingForceUpdate) {
      return;
    }

    var nextProps = this.props;
    if (this._pendingProps != null) {
      nextProps = this._pendingProps;
      this._processProps(nextProps);
      this._pendingProps = null;

      this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
      if (this.componentWillReceiveProps) {
        this.componentWillReceiveProps(nextProps, transaction);
      }
    }

    this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;

    var nextState = this._pendingState || this.state;
    this._pendingState = null;

    if (this._pendingForceUpdate ||
        !this.shouldComponentUpdate ||
        this.shouldComponentUpdate(nextProps, nextState)) {
      this._pendingForceUpdate = false;
      // Will set `this.props` and `this.state`.
      this._performComponentUpdate(nextProps, nextState, transaction);
    } else {
      // If it not determined that a component should not update, we still want
      // to set props and state.
      this.props = nextProps;
      this.state = nextState;
    }

    this._compositeLifeCycleState = null;
  },

Мы видим, что,this.componentWillReceiveProps(nextProps, transaction);заявление находится вvar nextState = this._pendingState || this.state;Раньше, а вопрос 1, также сказал,setState({count: this.state.count + 1})опять такиsetState({count: 10})До этого значение this._pendingState подвергнется двум поверхностным слияниям объектов и станет nextState, используемым для обновления дочернего компонента. Итак, конечное значение состояния дочернего компонента равно{count: 10}.

Ответ на вопрос 3:

Поскольку вызов componentWillReceiveProps() происходит в процессе «однократного обновления» родительского компонента, а «однократное обновление» родительского компонента происходит в «режиме пакетного обновления», setState в componentWillReceiveProps также то же, что и setState в handleChildClick, который выполняется асинхронно.Конкретная производительность: состояние слияния, обновление значения состояния слияния до _pendingState, а затем передача текущего экземпляра компонента в dirtyComponents. Наконец, после того, как текущее «обновление с одним зеркалом» будет завершено, flush dirtyComponents запросит обновление, когда работа переходит к самому себе. Опять же, потому что второй уровень не может быть пройден, он обречен на бесплодие.

Не изучая этот пример, мы могли бы интуитивно подумать, что обновление происходит так:

更新Child组件  ->  更新Parent组件  ->  更新Child组件

После некоторого расчесывания мы обнаружили, что это на самом деле произошло так:

先更新Child组件的_pendingState ->  再更新Parent组件的_pendingState  ->  最后在Parent组件的“一镜到底更新”过程中更新Child组件

Тема 3

const Parent = React.createClass({
    getInitialState(){
        return {
            count: 0
        }
    },
    render(){
        return React.DOM.button({},`I have been clicked ${this.state.count} times`)
    },
    componentDidMount() {
        this.setState({
            count: this.state.count + 1
        });
         this.setState({
            count: this.state.count + 1
        });
         this.setState({
            count: this.state.count + 1
        });
    }
})

Изучив две вышеупомянутые темы, я считаю, что нам не нужно тратить слишком много времени на объяснение того, что произошло с несколькими вызовами setState в этом примере. Поскольку текущий вызов setState находится в «режиме пакетного обновления», три setState по существу эквивалентны трем вызовам «component.performUpdateIfNecessary()». Поскольку никакие другие подкомпоненты не запрашивают обновления в середине, все, что можно сказать, «одно зеркальное обновление» трех родительских компонентов будет происходить по очереди. Давайте сделаем лог в функции жизненного цикла componentDidUpdate, чтобы доказать это:

componentDidUpdate(){
    console.log('into componentDidUpdate');
}

Если вы видите, дело в том, что произошло обновление трех компонентов.

Тема 4

    const Child = React.createClass({
    getInitialState(){
        return {
            count: 0
        }
    },
    componentWillReceiveProps(){
        this.setState({
            count: 10
        });
    },
    render(){
        return React.DOM.button({
            onClick: this.handleChildClick
        },`Child count ${this.state.count} times`)
    }
});

const Parent = React.createClass({
    getInitialState(){
        return {
            count: 0
        }
    },
    render(){
        return React.DOM.div({},`I have been clicked ${this.state.count} times`)
    },
    componentDidMount() {
        this.setState({
            count: this.state.count + 1
        });
    }
})

Очевидно, что в настоящее время не в «режиме пакетного обновления», вызовы setState всех родительских компонентов вызовут «одноразовое обновление». В этом примере мы сосредоточимся на изучении того, что происходит, когда дочерний компонент вызывает setState в функции жизненного цикла componentWillReceiveProps? Приведет ли это к «обновлению одного зеркала» с дочерним компонентом в качестве корневого компонента? Ниже мы продолжаем исследовать.

Давайте взглянем на исходный код метода ReactCompositeComponent._performUpdateIfNecessary, который вызывает функцию componentWillReceiveProps:

_performUpdateIfNecessary: function(transaction) {
    if (this._pendingProps == null &&
        this._pendingState == null &&
        !this._pendingForceUpdate) {
      return;
    }

    var nextProps = this.props;
    if (this._pendingProps != null) {
      nextProps = this._pendingProps;
      this._processProps(nextProps);
      this._pendingProps = null;

      this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
      if (this.componentWillReceiveProps) {
        this.componentWillReceiveProps(nextProps, transaction);
      }
    }

    this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;

    var nextState = this._pendingState || this.state;
    this._pendingState = null;

    if (this._pendingForceUpdate ||
        !this.shouldComponentUpdate ||
        this.shouldComponentUpdate(nextProps, nextState)) {
      this._pendingForceUpdate = false;
      // Will set `this.props` and `this.state`.
      this._performComponentUpdate(nextProps, nextState, transaction);
    } else {
      // If it is not determined that a component should not update, we still want
      // to set props and state.
      this.props = nextProps;
      this.state = nextState;
    }

    this._compositeLifeCycleState = null;
  },

Мы видим, что есть присвоение состоянию жизненного цикла компонента перед вызовом функции жизненного цикла componentWillReceiveProps:

this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;

Эта строка кода очень важна, поэтому сначала запишите ее. Поскольку в настоящее время он не находится в «режиме пакетного обновления», setState вызывается в componentWillReceiveProps, сначала обновляется _pendingState, и, наконец, он возвращается к соединению, о котором мы упоминали выше:component.performUpdateIfNecessary();. Это наш старый друг, и он снова и снова упоминается в приведенном выше примере. Это отправная точка для истинного процесса обновления компонента на уровне DOM. С этой отправной точки вы столкнетесь с тремя уровнями, которые также упоминались выше.

Начнем с первого уровня (в методе ReactCompositeComponent.performUpdateIfNecessary):

// ......
if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
  return;
}
// ......

Присмотревшись, обнаруживаем, что есть такое условиеcompositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS. Несколькими строками выше мы упомянули, что перед вызовом componentWillReceiveProps переменной CompositeLifeCycleState было присвоено значение, которое оказалось равнымCompositeLifeCycle.RECEIVING_PROPS. Так что тут ответ уже очевиден. То есть просто выполните условие, а затем выполните оператор return. Первый уровень не может быть пройден, поэтому ответ, который мы предположили перед изучением этого примера, «приведет ли он к «одному зеркальному обновлению» с дочерним компонентом в качестве корневого компонента»: «Нет».

Суммировать

После некоторых исследований мы можем сделать следующие выводы (только для reactv0.8.0):

  • В React есть два режима обновления: режим пакетного обновления и режим непакетного обновления. В режиме пакетного обновления setState выполняется асинхронно, в режиме непакетного обновления setState выполняется синхронно.
  • Понимание «пакета» должно быть следующим: несколько запросов на обновление одного и того же компонента объединяются в один запрос на обновление (т. е. создается окончательное состояние _pendingState), а запросы на обновление нескольких компонентов объединяются в одно обновление корневого компонента (т. е. есть только одно "одно зеркало до конца обновления").
  • В вызове ReactUpdates.batchedUpdates() включается режим пакетного обновления.
  • Независимо от того, в каком порядке вы запрашиваете обновления, React всегда гарантирует, что обновления будут обновляться в порядке от более высокого (компоненты уровня) к более низкому (компоненты уровня).
  • React должен обеспечить только одно «одно зеркальное обновление», установив три уровня:
1. 第一道:
if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
  return;
}

2. 第二道:
if (this._pendingProps == null && this._pendingState == null && !this._pendingForceUpdate) {
  return;
}

3. 第三道:
if (this._pendingForceUpdate || !this.shouldComponentUpdate || this.shouldComponentUpdate(nextProps, nextState)) {
  this._pendingForceUpdate = false;
  this._performComponentUpdate(nextProps, nextState, transaction);
} else {
  // If it is not determined that a component should not update, we still want
  // to set props and state.
  this.props = nextProps;
  this.state = nextState;
}

До сих пор 8820 написал слово, я считаю, что детали механизма SetState изучали почти одинаково. Я думаю, что для того, чтобы понять сущность механизма setState, первое, что нужно сделать, это поставить «setState», понимаемую как «requesttosetstate». Потому что имя не является вицельным фактом, я всегда чувствовал, что команда реагирует на эту API под названием «SetState», сколько недостаточно подходит. Возможно, имя достаточно короткое, кто знает ......

С какой целью мы должны думать о введении этого механизма? То есть уменьшить рендеринг ненужных компонентов. В частности, уменьшите ненужные операции DOM и вызовы функций, чтобы повысить производительность обновлений интерфейса.

Наконец, как я упоминал выше, мы можем разделить процесс обновления компонентов на четыре этапа:

  1. запросить обновление
  2. принять решение о согласии на просьбу
  3. Рекурсивный обход от родительского компонента к дочернему
  4. реальная манипуляция DOM

Схема обновления выглядит следующим образом

RCC:ReactCompositeComponent

RDC:ReactDOMComponent

RTC:ReactTextComponent

Очевидно, что механизм setState этого исследования действительно охватывает только первые два этапа. Что касается углубленного двухступенчатого тыла, зафиксируйте день, чтобы автор объяснил это более подробно сейчас.

Спасибо за чтение, хорошо идти.