Почему setState() асинхронен в React?

внешний интерфейс дизайн React.js MobX

предисловие

Я не знаю, задавался ли вам когда-нибудь этот вопрос в React.setState()为什么是异步的? я думаюsetState()Это синхронно, я запутался после того, как зная, что это асинхронно, и я даже ожидаю, что отреагирует наsetStateSync()API, такие как . У автора MobX тоже есть этот вопросMichel Weststrate, он считает, что часто слышанные ответы легко опровергнуть, и полагает, что это может быть историческим бременем, поэтому он открылissueСпросите об истинной причине. Наконец-то эта проблема получила ключевого члена ReactDan Abramov, ответ Дэна указывает на то, что это не исторический багаж, а продуманная конструкция.

Примечание. Этот пост основан на ответе Дэна, а не на переводе. Я пропустил много менее важных вещей, см. полный ответ Дэна.здесь.

текст

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

1. Обеспечьте внутреннюю согласованность

Во-первых, я думаю, мы все можем согласиться с тем, что отложенный и пакетный повторный рендеринг полезен и важен для оптимизации производительности, независимо отsetState()Это синхронно или асинхронно. Тогда даже пустьstateОбновление синхронизации,propsНет, потому что вы знаете только, когда родительский компонент повторно отображаетсяprops.

Текущий дизайн гарантирует, что объекты (состояние, реквизиты, ссылки), предоставляемые React, ведут себя и ведут себя согласованно. Почему это важно? Дэн поднял каштан:

Предположениеstateобновляется синхронно, то следующий код работает должным образом:

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

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

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // 在父组件中做同样的事

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

Однако следующий код не работает должным образом:

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

Это связано с тем, что в синхронной модели, хотяthis.stateнемедленно обновится, ноthis.propsНет. И мы не можем обновить сразу без повторного рендеринга родительского компонента.this.props. Если вы хотите обновить сейчасthis.props(т.е. немедленный повторный рендеринг родительского компонента), от батчинга нужно отказаться (в зависимости от ситуации может быть значительное падение производительности).

Так, чтобы решить такую ​​проблему, в реакцииthis.stateа такжеthis.propsОба обновляются асинхронно, и в приведенном выше примере как до, так и после рефакторинга будет напечатано 0. Это делает продвижение штата более безопасным.

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

2. Оптимизация производительности

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

React будет давать разные вызовы разнымsetState()Звонкам назначаются разные приоритеты. Источники вызовов включают обработку событий, сетевые запросы, анимацию и многое другое.

Дэн поднял еще один каштан. Предположим, вы находитесь в окне чата и набираете сообщение,TextBoxв компонентеsetState()Вызов должен быть применен немедленно. Однако пока вы печатали, вы получили новое сообщение. Лучшим способом справиться с этим может быть отложенный рендеринг новогоMessageBubbleкомпонент, который делает ваш ввод более плавным, вместо немедленного рендеринга новыхMessageBubbleКомпоненты блокируют потоки, вызывая дрожание и задержку ввода.

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

3. Больше возможностей

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

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

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

Оказывается, построение поверх текущей модели Reactкорректировка жизненного цикла, эта идея действительно может быть реализована.@acdliteРаботаем над этой функцией уже несколько недель и скоро выпустим ее.RFC(тоже гребля!).

Обратите внимание, что асинхронные обновленияstateЭто предпосылка, что это видение может быть реализовано. Если синхронизировать обновлениеstateНевозможно отобразить новую страницу за кулисами и при этом сохранить интерактивную старую страницу. Независимые обновления состояния между ними будут конфликтовать.

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