React: RFClarification: почему setState асинхронный?

Созданный на 11 нояб. 2017  ·  31Комментарии  ·  Источник: facebook/react

Некоторое время я пытался понять, почему setState асинхронный. И, не сумев найти на него ответа в прошлом, я пришел к выводу, что это было по историческим причинам и, вероятно, трудно изменить сейчас. Однако @gaearon указал, что есть ясная причина, поэтому мне любопытно узнать :)

В любом случае, вот причины, которые я часто слышу, но я думаю, что они не могут быть всем, поскольку им слишком легко противодействовать.

Async setState требуется для асинхронного рендеринга

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

Component.prototype.setState = (nextState) => {
  this.state = nextState
  if (!this.renderScheduled)
     setImmediate(this.forceUpdate)
}

Фактически, например, mobx-react разрешает синхронное присваивание наблюдаемым объектам и при этом учитывает асинхронный характер рендеринга.

Async setState необходим, чтобы знать, какое состояние было _rendered_

Другой аргумент, который я иногда слышу, заключается в том, что вы хотите рассуждать о состоянии, которое было _рендерировано_, а не о состоянии, которое было _запрошено_. Но я не уверен, что в этом принципе есть много достоинств. Концептуально мне это кажется странным. Рендеринг - это побочный эффект, состояние сводится к фактам. Сегодня мне 32 года, а в следующем году мне исполнится 33, независимо от того, удастся ли компоненту-владельцу выполнить повторный рендеринг в этом году или нет :).

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

Интересные наблюдения: за 2 года mobx-react никто не задавал мне вопрос: как я узнаю, что мои наблюдаемые отображаются? Просто этот вопрос часто кажется неактуальным.

Я встречал несколько случаев, когда было важно знать, какие данные были обработаны. Я помню случай, когда мне нужно было знать размеры некоторых данных в пикселях для целей макета. Но это было элегантно решено с помощью didComponentUpdate и на самом деле не нужно было полагаться на асинхронность setState . Эти случаи кажутся настолько редкими, что вряд ли оправдано проектирование api в первую очередь на них. Если это можно как- то сделать, думаю, хватит


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

Discussion

Самый полезный комментарий

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

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

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

Вы спрашиваете: почему мы не можем сделать то же самое (группирование), но сразу записать обновления setState в this.state не дожидаясь окончания согласования. Я не думаю, что есть один очевидный ответ (у любого решения есть компромиссы), но вот несколько причин, которые я могу придумать.

Гарантия внутренней согласованности

Даже если state обновляется синхронно, props - нет. (Вы не можете узнать props пока не повторно визуализируете родительский компонент, и если вы сделаете это синхронно, пакетирование прекратится.)

Сейчас объекты, предоставляемые React ( state , props , refs ), внутренне согласованы друг с другом. Это означает, что если вы используете только эти объекты, они гарантированно будут ссылаться на полностью согласованное дерево (даже если это более старая версия этого дерева). Почему это важно?

Когда вы используете только состояние, если оно сбрасывается синхронно (как вы предлагали), этот шаблон будет работать:

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(); // Does the same thing in a parent

Я хочу подчеркнуть, что в типичных приложениях React, которые полагаются на setState() это единственный наиболее распространенный вид специфичного для React рефакторинга, который вы будете выполнять ежедневно .

Однако это нарушает наш код!

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 без повторного рендеринга родителя, что означает, что нам придется отказаться от пакетной обработки (что, в зависимости от случая, может очень сильно ухудшить производительность).

Есть также более тонкие случаи того, как это может сломаться, например, если вы смешиваете данные из props (еще не сброшены) и state (предлагается немедленно очистить) для создания нового состояния. : https://github.com/facebook/react/issues/122#issuecomment -81856416. Ссылки представляют ту же проблему: https://github.com/facebook/react/issues/122#issuecomment -22659651.

Эти примеры вовсе не теоретические. На самом деле привязки React Redux раньше имели именно такую ​​проблему, потому что они смешивают реквизиты React с состоянием без React: https://github.com/reactjs/react-redux/issues/86 , https://github.com/ responsejs / response-redux / pull / 99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , . com / reactjs / react-redux / issues / 525.

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

Так как же React решает эту проблему сегодня? В React и this.state и this.props обновляются только после согласования и промывки, поэтому вы увидите, что 0 печатается как до, так и после рефакторинга. Это делает подъем состояния безопасным.

Да, в некоторых случаях это может быть неудобно. Специально для людей, пришедших из большего количества объектов объектно-ориентированного программирования, которые просто хотят несколько раз изменить состояние, вместо того, чтобы думать, как представить полное обновление состояния в одном месте. Я могу сочувствовать этому, хотя я действительно думаю, что сохранение концентрированных обновлений состояния более понятно с точки зрения отладки: https://github.com/facebook/react/issues/122#issuecomment -19888472.

Тем не менее, у вас есть возможность переместить состояние, которое вы хотите немедленно прочитать, в какой-нибудь изменяемый объект, особенно если вы не используете его как источник истины для рендеринга. Это в значительной степени то, что MobX позволяет вам делать 🙂.

У вас также есть возможность промыть все дерево, если вы знаете, что делаете. API называется ReactDOM.flushSync(fn) . Я не думаю, что мы это еще задокументировали, но мы обязательно сделаем это в какой-то момент во время цикла выпуска 16.x. Обратите внимание, что на самом деле он принудительно выполняет полный повторный рендеринг для обновлений, которые происходят внутри вызова, поэтому вам следует использовать его очень экономно. Таким образом, это не нарушает гарантии внутренней согласованности между props , state и refs .

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

Включение одновременных обновлений

Концептуально React ведет себя так, как если бы у него была одна очередь обновлений для каждого компонента. Вот почему обсуждение вообще имеет смысл: мы обсуждаем, применять ли обновления к this.state немедленно или нет, потому что мы не сомневаемся, что обновления будут применяться именно в этом порядке. Однако этого не должно быть ( ха-ха ).

В последнее время мы много говорим об «асинхронном рендеринге». Я признаю, что мы не очень хорошо постарались объяснить, что это означает, но такова природа НИОКР: вы идете за идеей, которая кажется концептуально многообещающей, но вы действительно понимаете ее значение только после того, как потратили на нее достаточно времени.

Один из способов объяснения «асинхронного рендеринга» заключается в том, что React может назначать разные приоритеты для вызовов setState() зависимости от того, откуда они исходят: обработчик событий, сетевой ответ, анимация и т . Д.

Например, если вы набираете сообщение, setState() звонков в TextBox компоненты необходимости промывать немедленно. Однако, если вы получаете новое сообщение во время набора текста , вероятно, лучше отложить рендеринг нового MessageBubble до определенного порога (например, секунды), чем допускать прерывание набора текста из-за блокировки нить.

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

Я знаю, что такая оптимизация производительности может показаться не очень захватывающей или убедительной. Вы могли бы сказать: «Нам это не нужно с MobX, наше отслеживание обновлений достаточно быстрое, чтобы просто избежать повторного рендеринга». Я не думаю, что это верно во всех случаях (например, независимо от того, насколько быстр MobX, вам все равно придется создавать узлы DOM и выполнять рендеринг для вновь смонтированных представлений). Тем не менее, если бы это было правдой, и если бы вы сознательно решили, что у вас все в порядке, всегда оборачивая объекты в определенную библиотеку JavaScript, которая отслеживает чтение и запись, возможно, вы не так сильно выиграете от этой оптимизации.

Но асинхронный рендеринг - это не только оптимизация производительности.

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

Однако, если навигация выполняется достаточно быстро (в течение секунды или около того), мигание и немедленное скрытие счетчика ухудшает работу пользователя. Хуже того, если у вас есть несколько уровней компонентов с разными асинхронными зависимостями (данные, код, изображения), вы получите каскад счетчиков, которые на короткое время мигают один за другим. Это неприятно как визуально, так и на практике замедляет работу вашего приложения из-за всех перекомпоновок DOM. Это также источник большого количества шаблонного кода.

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

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

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

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

Все 31 Комментарий

Мы все ждем @gaearon .

@Kaybarax Привет, сейчас выходные ;-)

@mweststrate Ой! моя вина. Прохладный.

Здесь я рискну и скажу, что это из-за того, что несколько setState объединены в один тик.

На следующей неделе я ухожу в отпуск, но, вероятно, поеду во вторник, поэтому постараюсь ответить в понедельник.

function enqueueUpdate (component) {
sureInjected ();

// Различные части нашего кода (например, ReactCompositeComponent's
// _renderValidatedComponent) предполагаем, что вызовы для рендеринга не вложены;
// проверяем, что это так. (Это вызывается каждым обновлением верхнего уровня
// функция, такая как setState, forceUpdate и т. д .; создание и
// разрушение компонентов верхнего уровня охраняется в ReactMount.)

if (! batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates (enqueueUpdate, компонент);
возвращение;
}

dirtyComponents.push (компонент);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}

@mweststrate всего 2 цента: это очень правильный вопрос.
Я уверен, что мы все согласны с тем, что было бы намного проще рассуждать о состоянии, если бы setState был синхронным.
Какими бы ни были причины сделать setState асинхронным, я не уверен, что команда хорошо отреагирует на это по сравнению с недостатками, которые могут возникнуть, например, с трудностью рассуждать о текущем состоянии и путаницей, которая приносит разработчикам.

@mweststrate интересно, что я задал тот же вопрос здесь: https://discuss.reactjs.org/t/historic-reasons-behind-setstate-not-being-immediately-visible/8487

Я лично встречал и видел у других разработчиков недоразумение по этому поводу. @gaearon было бы здорово получить объяснение по этому

Извините, сейчас конец года, и мы немного отстаем от GitHub и т. Д., Пытаясь завершить все, над чем мы работали до праздников.

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

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

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

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

Вы спрашиваете: почему мы не можем сделать то же самое (группирование), но сразу записать обновления setState в this.state не дожидаясь окончания согласования. Я не думаю, что есть один очевидный ответ (у любого решения есть компромиссы), но вот несколько причин, которые я могу придумать.

Гарантия внутренней согласованности

Даже если state обновляется синхронно, props - нет. (Вы не можете узнать props пока не повторно визуализируете родительский компонент, и если вы сделаете это синхронно, пакетирование прекратится.)

Сейчас объекты, предоставляемые React ( state , props , refs ), внутренне согласованы друг с другом. Это означает, что если вы используете только эти объекты, они гарантированно будут ссылаться на полностью согласованное дерево (даже если это более старая версия этого дерева). Почему это важно?

Когда вы используете только состояние, если оно сбрасывается синхронно (как вы предлагали), этот шаблон будет работать:

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(); // Does the same thing in a parent

Я хочу подчеркнуть, что в типичных приложениях React, которые полагаются на setState() это единственный наиболее распространенный вид специфичного для React рефакторинга, который вы будете выполнять ежедневно .

Однако это нарушает наш код!

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 без повторного рендеринга родителя, что означает, что нам придется отказаться от пакетной обработки (что, в зависимости от случая, может очень сильно ухудшить производительность).

Есть также более тонкие случаи того, как это может сломаться, например, если вы смешиваете данные из props (еще не сброшены) и state (предлагается немедленно очистить) для создания нового состояния. : https://github.com/facebook/react/issues/122#issuecomment -81856416. Ссылки представляют ту же проблему: https://github.com/facebook/react/issues/122#issuecomment -22659651.

Эти примеры вовсе не теоретические. На самом деле привязки React Redux раньше имели именно такую ​​проблему, потому что они смешивают реквизиты React с состоянием без React: https://github.com/reactjs/react-redux/issues/86 , https://github.com/ responsejs / response-redux / pull / 99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , . com / reactjs / react-redux / issues / 525.

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

Так как же React решает эту проблему сегодня? В React и this.state и this.props обновляются только после согласования и промывки, поэтому вы увидите, что 0 печатается как до, так и после рефакторинга. Это делает подъем состояния безопасным.

Да, в некоторых случаях это может быть неудобно. Специально для людей, пришедших из большего количества объектов объектно-ориентированного программирования, которые просто хотят несколько раз изменить состояние, вместо того, чтобы думать, как представить полное обновление состояния в одном месте. Я могу сочувствовать этому, хотя я действительно думаю, что сохранение концентрированных обновлений состояния более понятно с точки зрения отладки: https://github.com/facebook/react/issues/122#issuecomment -19888472.

Тем не менее, у вас есть возможность переместить состояние, которое вы хотите немедленно прочитать, в какой-нибудь изменяемый объект, особенно если вы не используете его как источник истины для рендеринга. Это в значительной степени то, что MobX позволяет вам делать 🙂.

У вас также есть возможность промыть все дерево, если вы знаете, что делаете. API называется ReactDOM.flushSync(fn) . Я не думаю, что мы это еще задокументировали, но мы обязательно сделаем это в какой-то момент во время цикла выпуска 16.x. Обратите внимание, что на самом деле он принудительно выполняет полный повторный рендеринг для обновлений, которые происходят внутри вызова, поэтому вам следует использовать его очень экономно. Таким образом, это не нарушает гарантии внутренней согласованности между props , state и refs .

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

Включение одновременных обновлений

Концептуально React ведет себя так, как если бы у него была одна очередь обновлений для каждого компонента. Вот почему обсуждение вообще имеет смысл: мы обсуждаем, применять ли обновления к this.state немедленно или нет, потому что мы не сомневаемся, что обновления будут применяться именно в этом порядке. Однако этого не должно быть ( ха-ха ).

В последнее время мы много говорим об «асинхронном рендеринге». Я признаю, что мы не очень хорошо постарались объяснить, что это означает, но такова природа НИОКР: вы идете за идеей, которая кажется концептуально многообещающей, но вы действительно понимаете ее значение только после того, как потратили на нее достаточно времени.

Один из способов объяснения «асинхронного рендеринга» заключается в том, что React может назначать разные приоритеты для вызовов setState() зависимости от того, откуда они исходят: обработчик событий, сетевой ответ, анимация и т . Д.

Например, если вы набираете сообщение, setState() звонков в TextBox компоненты необходимости промывать немедленно. Однако, если вы получаете новое сообщение во время набора текста , вероятно, лучше отложить рендеринг нового MessageBubble до определенного порога (например, секунды), чем допускать прерывание набора текста из-за блокировки нить.

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

Я знаю, что такая оптимизация производительности может показаться не очень захватывающей или убедительной. Вы могли бы сказать: «Нам это не нужно с MobX, наше отслеживание обновлений достаточно быстрое, чтобы просто избежать повторного рендеринга». Я не думаю, что это верно во всех случаях (например, независимо от того, насколько быстр MobX, вам все равно придется создавать узлы DOM и выполнять рендеринг для вновь смонтированных представлений). Тем не менее, если бы это было правдой, и если бы вы сознательно решили, что у вас все в порядке, всегда оборачивая объекты в определенную библиотеку JavaScript, которая отслеживает чтение и запись, возможно, вы не так сильно выиграете от этой оптимизации.

Но асинхронный рендеринг - это не только оптимизация производительности.

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

Однако, если навигация выполняется достаточно быстро (в течение секунды или около того), мигание и немедленное скрытие счетчика ухудшает работу пользователя. Хуже того, если у вас есть несколько уровней компонентов с разными асинхронными зависимостями (данные, код, изображения), вы получите каскад счетчиков, которые на короткое время мигают один за другим. Это неприятно как визуально, так и на практике замедляет работу вашего приложения из-за всех перекомпоновок DOM. Это также источник большого количества шаблонного кода.

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

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

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

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

Замечательное подробное объяснение решений, лежащих в основе архитектуры React. Спасибо.

отметка

Спасибо, Дэн.

Я этот вопрос. Замечательный вопрос и отличный ответ. Мне всегда казалось, что это плохое дизайнерское решение, теперь надо переосмыслить 😄

Спасибо, Дэн.

Я называю это asyncAwesome setState: smile:

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

@gaearon спасибо за подробное объяснение! Меня это уже давно придирает («должна быть веская причина, но никто не может сказать, какая именно»). Но теперь это имеет смысл, и я понимаю, что это действительно осознанное решение :). Большое спасибо за подробный ответ, очень признателен!

Или, может быть, они не так много читают из реквизита, а вместо этого читают напрямую из изменяемых объектов MobX.

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

Еще раз большое спасибо!

@gaearon Спасибо за подробное и отличное объяснение.
Хотя здесь все еще чего-то не хватает, и я думаю, что понимаю, но хочу быть уверенным.

Когда событие регистрируется как «Outside React», это означает, например, что через addEventListener в ссылке. Тогда дозирование не происходит.
Рассмотрим этот код:

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

  componentDidMount() {
    this.refBtn.addEventListener("click", this.onClick);
  }

  componentWillUnmount() {
    this.refBtn.removeEventListener("click", this.onClick);
  }

  onClick = () => {
    console.log("before setState", this.state.count);
    this.setState(state => ({ count: state.count + 1 }));
    console.log("after setState", this.state.count);
  };

  render() {
    return (
      <div>
        <button onClick={this.onClick}>React Event</button>
        <button ref={ref => (this.refBtn = ref)}>Direct DOM event</button>
      </div>
    );
  }
}

Когда мы нажмем на кнопку «React Event», мы увидим в консоли:
"before setState" 0
"after setState" 0
Когда будет нажата другая кнопка «Прямое событие DOM», мы увидим в консоли:
"before setState" 0
"after setState" 1

После небольшого исследования и просмотра исходного кода, я думаю, что знаю, почему это происходит.
response не может полностью контролировать поток события и не может быть уверен в том, когда и как сработает следующее событие, поэтому в «режиме паники» он просто немедленно вызывает изменение состояния.

Что ты думаешь по этому поводу? : мышление:

@ sag1v хотя и немного связан, но, вероятно, будет

@ sag1v @gaearon дал мне очень краткий ответ здесь https://twitter.com/dan_abramov/status/949992957180104704 . Я думаю, что его мнение по этому поводу также ответит мне более конкретно.

@mweststrate Я думал открыть новый выпуск, но потом понял, что это напрямую связано с вашим вопросом «почему setState асинхронный?».
Поскольку это обсуждение касается решений, принятых при создании setState "async", я подумал добавить, когда и почему нужно делать "синхронизацию".
Я не против открыть новый выпуск, если не убедил вас, что мой пост связан с этой проблемой: wink:

@Kaybarax Это потому, что ваш вопрос был "_When is sync_", а не "_ Почему это sync_" ?.
Как я уже упоминал в своем сообщении, я думаю ,

React не может полностью контролировать поток события и не может быть уверен в том, когда и как сработает следующее событие, поэтому в «режиме паники» он просто немедленно вызовет изменение состояния

Вроде, как бы, что-то вроде. Хотя это точно не связано с вопросом об обновлении this.state .

Вы спрашиваете, в каких случаях React включает пакетную обработку. В настоящее время React выполняет пакетные обновления внутри

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

В будущих версиях React это поведение изменится. План состоит в том, чтобы обрабатывать обновления как с низким приоритетом по умолчанию, чтобы они в конечном итоге объединялись и группировались вместе (например, в течение секунды), с возможностью немедленной их очистки. Вы можете прочитать больше здесь: https://github.com/facebook/react/issues/11171#issuecomment -357945371.

Потрясающий!

Этот вопрос и ответ следует задокументировать в более доступном месте. Спасибо, ребята, просветившие нас.

Многому научился. Спасибо

Пытаюсь добавить в ветку свою точку зрения. Я работаю над приложением на основе MobX несколько месяцев, я много лет изучаю ClojureScript и создал свою собственную альтернативу React (под названием Respo), я пробовал Redux в первые дни, хотя и очень короткое время, и я делаю ставку на ReasonML.

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

React не является чисто функциональным. Охватывая локальные состояния внутри компонентов, React имеет возможность взаимодействовать с различными библиотеками, связанными с DOM и другими API-интерфейсами браузера (также дружественными для MobX), что тем временем делает React нечистым. Однако я пробовал в ClojureScript, если React чистый, это может быть катастрофой, так как очень сложно взаимодействовать с таким количеством существующих библиотек, в которых есть побочные эффекты.

Итак, в Respo (мое собственное решение) у меня были две цели, которые кажутся противоречивыми: 1) view = f(store) поэтому локального состояния не ожидается; 2) Мне не нравится программировать все состояния пользовательского интерфейса компонентов в глобальных редукторах, так как это может быть сложно поддерживать. В конце концов, я понял, что мне нужен синтаксический сахар, который помогает мне поддерживать состояния компонентов в глобальном хранилище с помощью paths , в то время как я пишу обновления состояния внутри компонента с помощью макросов Clojure.

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

Кстати, я говорил о будущем React и его асинхронных функциях, на случай, если вы его пропустили:
https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html

Дэн, ты мой кумир ..... большое спасибо.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги