Я работаю над React.
TLDR:
Но можете ли вы доверять 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 для синхронного сброса изменений). Пока мы не переключим поведение по умолчанию (возможно, в 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
});
Все внутренние обработчики событий React обернуты unstable_batchedUpdates
, поэтому по умолчанию они сгруппированы. Обратите внимание, что двойная упаковка обновления не unstable_batchedUpdates
имеет никакого эффекта. Обновления сбрасываются, когда мы выходим из самого внешнего unstable_batchedUpdates
вызова.
Этот API «нестабилен» в том смысле, что мы удалим его, когда пакетная обработка уже включена по умолчанию. Однако мы не будем удалять его в младшей версии, поэтому вы можете смело полагаться на него до React 17, если вам нужно принудительно использовать пакетную обработку в некоторых случаях за пределами обработчиков событий React.
Подводя итог, это сбивает с толку, потому что React по умолчанию выполняет пакетные операции только внутри обработчиков событий. Это изменится в будущих версиях, и тогда поведение будет более простым. Но решение состоит не в том, чтобы пакетировать меньше , а по умолчанию - больше . Вот что мы собираемся делать.