Ответ 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 только портирует внутри обработчиков событий по умолчанию. Это изменится в будущих версиях, и тогда поведение будет более простым. Но решение состоит не в том, чтобы делать меньше, а в том, чтобы больше портировать по умолчанию. Это то, что мы собираемся делать.