Поддерживает ли React порядок обновления состояния?

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

  1. тот же компонент?
  2. разные компоненты?

Рассмотрите возможность нажатия кнопки в следующих примерах:

1. Существует ли когда-либо вероятность того, что a является ложным, а b верно для:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2. Существует ли когда-либо вероятность того, что a является ложным, а b верно для:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

Имейте в виду, что это экстремальные упрощения моего варианта использования. Я понимаю, что могу сделать это по-другому, например, обновлять оба параметра состояния одновременно в примере 1, а также выполнять второе обновление состояния в обратном вызове к первому обновлению состояния в примере 2. Однако это не мой вопрос, и меня интересует только если есть четко определенный способ, которым React выполняет эти обновления состояния, ничего другого.

Любой ответ, подкрепленный документацией, высоко оценен.

Ответ 1

Я работаю над Реактивом.

TL;DR:

Но можете ли вы доверять React для обновления состояния в том же порядке, что и setState, для

  • тот же компонент?

Да.

  • разные компоненты?

Да.

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

В настоящее время (React 16 и более ранние версии) по умолчанию загружаются только обновления внутри обработчиков событий React. Существует нестабильный API для принудительной дозировки вне обработчиков событий в редких случаях, когда вам это нужно.

В будущих версиях (возможно, React 17 и более поздних) React будет загружать все обновления по умолчанию, поэтому вам не придется об этом думать. Как всегда, мы сообщим о любых изменениях об этом в блоге React и в примечаниях к выпуску.


Ключом к пониманию этого является то, что независимо от того, сколько setState() вызывает количество компонентов, которые вы делаете внутри обработчика событий React, они будут производить только один повторный рендер в конце события. Это очень важно для хорошей производительности в больших приложениях, потому что если Child и Parent каждого вызов setState() при обработке события щелчка, вы не хотите, чтобы пересборка Child дважды.

В обоих примерах setState() происходят внутри обработчика событий React. Поэтому они всегда сбрасываются вместе в конце события (и вы не видите промежуточное состояние).

Обновления всегда мелко сливаются в том порядке, в котором они происходят. Поэтому, если первое обновление - {a: 10}, второе - {b: 20}, а третье - {a: 30}, отображаемое состояние будет {a: 30, b: 20}. Более последнее обновление к тому же состоянию ключа (например, как в моем примере) всегда "выигрывает". a

Объект this.state обновляется, когда мы повторно визуализируем пользовательский интерфейс в конце пакета. Поэтому, если вам нужно обновить состояние на основе предыдущего состояния (например, увеличивая счетчик), вы должны использовать функциональную setState(fn) которая дает вам предыдущее состояние, вместо чтения из this.state. Если вам интересно об этом, я подробно объяснил это в этом комментарии.


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

Тем не менее, как в React 16, так и в более ранних версиях, до сих пор нет пакетной обработки по умолчанию вне обработчиков событий React. Поэтому, если в вашем примере у нас был обработчик ответа AJAX вместо handleClick, каждый setState() будет обрабатываться немедленно, когда это произойдет. В этом случае, да, вы увидели бы промежуточное состояние:

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

Мы понимаем, что неудобно, что поведение различно в зависимости от того, находитесь ли вы в обработчике событий или нет. Это изменится в будущей версии React, которая по умолчанию будет загружать все обновления (а также предоставлять API-интерфейс opt-in для синхронного изменения синхронизации). Пока мы не переключим поведение по умолчанию (возможно, в React 17), существует API, который вы можете использовать для принудительной доработки:

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

Обработчики событий Internally React все завернуты в unstable_batchedUpdates поэтому они по умолчанию делятся. Обратите внимание, что обертка обновления в unstable_batchedUpdates дважды не влияет. Обновления очищаются при выходе из внешнего вызова unstable_batchedUpdates.

Этот API "нестабилен" в том смысле, что мы удалим его, когда пакетная обработка уже включена по умолчанию. Тем не менее, мы не удалим его в младшей версии, так что вы можете смело полагаться на него, пока не будете реагировать 17, если вам нужно принудительно выполнить пакетную обработку в некоторых случаях вне обработчиков событий React.


Подводя итог, это запутанная тема, потому что React только портирует внутри обработчиков событий по умолчанию. Это изменится в будущих версиях, и тогда поведение будет более простым. Но решение состоит не в том, чтобы делать меньше, а в том, чтобы больше портировать по умолчанию. Это то, что мы собираемся делать.

Ответ 2

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

1) Если вы делаете это

this.setState({ a: true });
this.setState({ b: true });

Я не думаю, что будет ситуация, когда a будет true а b будет false из-за пакетирования.

Однако, если b зависит от a тогда действительно может возникнуть ситуация, когда вы не получите ожидаемое состояние.

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

После обработки всех вышеуказанных вызовов this.state.value будет равно 1, а не 3, как вы ожидаете.

Это упоминается в статье: setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

Это даст нам this.state.value === 3

Ответ 3

как в документе

setState() устанавливает изменения в состоянии компонента и сообщает React, что этот компонент и его дочерние элементы должны быть повторно отображены с обновленным состоянием. Это основной метод, который вы используете для обновления пользовательского интерфейса в ответ на обработчики событий и ответы сервера.

он будет преформировать изменение, как в очереди (FIFO: First In First Out), первый вызов будет первым для заготовки

Ответ 4

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

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

https://reactjs.org/docs/react-component.html