Из утопии путешествий во времени взгляните на ошибки проектирования управления состоянием

React.js Redux MobX RxJS

Философия управления состоянием Redux очень элегантна, и поддержка отладки во времени, которая поставляется с ней, также очень крутая. Но является ли эта функция легендарной серебряной пулей и какую дополнительную нагрузку она принесет пользователю? Давайте подумаем еще раз.

Что такое путешествие во времени?

На React Europe 2015 Дэн Абрамов продемонстрировал, как Redux DevTools может улучшить процесс отладки, позволяя разработчикам свободно перемещаться по истории.Demo, опыт использования этого инструмента очень впечатляет, и он получил очень хороший отклик. С тех пор библиотеки управления состоянием, такие как Vuex и MobX, последовательно внедряли поддержку аналогичной функциональности в свои инструменты отладки.

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

  • Объедините локальное состояние в глобальное хранилище для управления состоянием.
  • В среду разработки устанавливаются DevTools, поддерживающие библиотеку управления состоянием, или вводятся специальные компоненты мониторинга.
  • Горячая перезагрузка HMR с включенным Webpack в среде разработки.

Важно отметить, что эта функция полностьюпри отладкев использовании. Однако, поскольку эта способность настолько впечатляет, она также стала одной из основных причин, по которой многие люди переходят на стек технологий React + Redux: красивая концептуальная модель в сочетании с прекрасным опытом отладки, это решение — просто артефакт! Подобно тому, как React был первым, кто реализовал декларативный рендеринг в браузере, Redux также был первым, кто реализовал идеальный опыт отладки в браузере.Эти оригинальные работы внесли большой вклад в область внешнего интерфейса. Далее наш анализ некоторых потенциальных проблем с React + Redux также основан на уважении к работе сообщества.

Почему вам не нужны путешествия во времени?

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

Традиционно мы использовали функциональность в качестве параметра для дифференциации категорий приложений. Например: история управления, событие H5, обмен мгновенными сообщениями в чате, покупки в электронной коммерции, прямая видеотрансляция... У нас есть много подсекторов, каждый из которых имеет разные болевые точки и направления бизнеса. Очень сложно открыть два вены Рен и Ду. Но есть ли более простой способ разделить его? Здесь у нас есть более простой ответ, который просто делит сложные интерфейсные приложения на две категории:управляемый даннымиа такжеуправляемый событиями.

Интерфейсные приложения, управляемые данными

Бизнес-сложность таких приложений полностью обусловленаБесконечные данные и сложные бизнес-процессы в фоновом режиме. Например, страница просмотра веб-сайта с покупками не требует большого количества входных данных для обработки, но данные о продукте из внутреннего интерфейса могут быть тысячами людей; другим примером является платформа бронирования 12306, хотя ее внешний интерфейс просто, но сложность всего бизнес-процесса не может представить себе рядовой пользователь или даже разработчик. Вообще говоря, такого рода приложения, которые позволяют пользователям заполнить несколько форм и проверочных кодов максимум, насколько глубока яма в бизнес-логике, часто понимают только студенты, которые прикоснулись к ней. Эти приложения можно понимать какуправляемый даннымииз.

Интерфейсные приложения, управляемые событиями

В отличие от интерфейсного приложения, управляемого событиями, его сложность исходит изсобытие пользовательского ввода. Например, даже если редактор форматированного текста вообще не подключается к фоновому интерфейсу при редактировании, он может стать легендарным «Tiankeng», просто обрабатывая пользовательские вставки, выбор и события клавиатуры; другим примером является версия «Taikoda» для H5. «Людям» нужно только вытягивать из бэкенда статические музыкальные ресурсы, но пока ритм щелчка пользователя составляет всего несколько десятков миллисекунд, состояние интерфейса и конечный результат могут быть совершенно другими. При построении этого типа приложения сложность в основном связана с тем, что большое количество различных типов асинхронных событий может быть организовано и объединено произвольно, что делает возможное пространство состояний чрезвычайно раздутым и подверженным ошибкам - я считаю, что до тех пор, пока несколько таймеров поддерживаются на странице одновременно.Студенты могут понять. Мы можем классифицировать такие приложения какуправляемый событиямииз.

Путешествие во времени и классификация приложений

Какое отношение концепция путешествия во времени имеет к двум упомянутым выше категориям приложений? Это включает в себя множество мотивов для принятия решения об использовании Redux при выборе технологии:Инструменты разработки Redux могут поддерживать путешествия во времени, поэтому, когда наше приложение сталкивается с похожими сценариями, требующими отслеживания состояния с возвратом, риск запуска в Redux меньше.

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

  • интерфейсные приложения, управляемые данными,Возможность путешествовать во времени почти полностью не нужна. Поскольку данные из серверной части являются фактическим Единственным Источником Истины, операция обратного отслеживания, основанная на инструменте управления состоянием во внешнем интерфейсе, очень легко разрушит эту зависимость от источника данных, что приведет к несогласованному состоянию между интерфейсом и серверной частью. Вот очень простой пример: если страница формы фонового приложения управления поддерживает перемещение во времени, то воспроизведение «путешествия» события отправки формы, очевидно, приведет к повторным запросам POST, что не являетсяидемпотентВ это время путешествие во времени интерфейса будет даже нарушать концепцию RESTful.
  • интерфейсные приложения, управляемые событиями,Очень сильная зависимость от технологии путешествий во времени. Почти все надежные текстовые редакторы на рынке поддерживают собственный набор стеков отмены — это основная функция путешествий во времени! В качестве другого примера, функции сохранения и чтения игрового прогресса также являются типичными функциями путешествий во времени. Для этого типа приложений путешествие во времени является даже одним из основных факторов, влияющих на опыт: редактор форматированного текста, формат содержимого которого будет необъясним после отзыва, как пользователи могут доверять ему? Не говоря уже об игре, которая не может прочитать предыдущий прогресс. Даже если функция отмены хорошо реализована, пользователи могут отменить себя, когда столкнутся с неожиданным поведением или даже ошибками редактора, а затем попробовать другие методы взаимодействия для достижения своих целей — путешествия во времени — последний хранитель пользовательского опыта!

Из приведенного выше обсуждения мы можем сделать вывод, что только дляуправляемый событиямиДля интерфейсных приложений функция путешествия во времени имеет смысл (и чрезвычайно важна!). А за управленческий бэкграунд и т.д.управляемый даннымиДля интерфейсных приложений путешествие во времени — это просто вишенка на торте.

Я полагаю, что многие студенты будут утверждать здесь, что есть много успешных случаев использования Redux для управления внутренним бизнесом.Вы думаете, что все их архитекторы неправы? Кроме того, у Redux есть масса дополнительных преимуществ, помимо путешествий во времени, которые гораздо важнее путешествий во времени при принятии решений! Это правда, что популярность Redux доказала, что он может поддерживать «крупномасштабные» интерфейсные приложения, но дизайн фреймворка должен сопровождаться компромиссом.В бизнес-сценарии, не требующем путешествий во времени, некоторые фреймворки, представленные в Redux для реализации путешествий во времени, принесут дополнительные проблемы.Таким образом, вопрос, который мы хотим обсудить ниже, является: что пожертвовал redux, чтобы быть первым, кто реализует функцию Time Travel?

Каково бремя стека технологий путешествий во времени?

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

- «Королева суровых»

Когда я только что обнаружил, что Redux может полностью решить проблему передачи реквизита слой за слоем в React, все были очень взволнованы: вау, этот компонент без состояния такой элегантный! Вау, пока все статусы упоминаются в магазине, мы можем плавно откатиться во время разработки! Вскоре появились две лучшие практики:

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

Итак, каковы проблемы с приложениями, разработанными в соответствии с этими двумя лучшими практиками?

Антишаблон для глобального состояния

Под искушением путешествия во времени сдать все состояние в управление магазину, а потом убить его напрочьsetStateЭто действительно заманчиво: он не только отлично поддерживает путешествия во времени, но и решает, казалось бы, досадную проблему в React. Однако, когда все состояние передано в управление магазину, без питов не обойтись.В настоящее время мнение Redux по этому поводу в официальной документации таковоThere is no "right" answer for this, то есть практику упоминания всего состояния в магазине тоже можно считать разумной. Но так ли это на самом деле?

Я не знаю, сколько студентов слышали это предупреждение от своих предшественников, когда впервые изучали программирование:Избегайте глобальных переменных. А кажущееся высокоуровневым глобальное состояние в стеке технологий React — это просто новая глобальная переменная, украшенная Context — вы думаете, вы ее носилиstoreРазве люди в жилетах тебя не знают? Проблема с глобальными переменными заключается в том, что ни одно из глобальных состояний не может выйти:

  • Глобальное состояние очень легко вызватьконфликт имен, что очень очевидно в плоском хранилище: различные фреймворки пакетов Redux второго порядка часто любят определять некоторые из своих собственных соглашений об именах, чтобы обеспечить «согласованность», но если вы назовете это, вы не сможете передать область языка. само по себе, но необходимость полагаться на хрупкие соглашения, чтобы гарантировать, что это явно искусственно увеличивает нагрузку на мышление: понятно использовать венгерскую нотацию на языке ассемблера без механизма области видимости, но она все еще поддерживается в разработке программного обеспечения в 2017 году. Разве такого рода соглашения действительно не обращают вспять историю? - Конечно, нет! Может ли язык ассемблера поддерживать путешествия во времени?
  • Глобальное состояние трудно выразитьвложенные типы данных. Обновление в семейном сегменте Redux{a: {b: {c: {d: 1 }}}}Практически всегда требуются вспомогательные инструменты. Для редактора форматированного текста, если вы хотите выразить информацию о том, что «в таблице поддерживаются вложенные таблицы», собственная структура данных JSON, соответствующая Redux, также очень тонкая, и в основном вы должны использовать Immutable — но почему бы и нет? используйте Immutable напрямую, а как насчет того, чтобы пропустить слой Redux? Slate.js, с которым я столкнулся, делает именно это. О, вы имели в виду нативный Draft.js Facebook? Да, он использует Immutable, но реализует элегантную плоскую структуру данных и никогда не поддерживает псевдотребование таблиц.
  • Модель памяти глобального состояния не соответствует классическойКомпьютерная архитектура. Для настольного графического интерфейса, который намного сложнее, чем веб-страница в браузере, является ли соответствующее пространство памяти процесса, соответствующего каждому окну, независимым друг от друга, или оно смешивается с «глобальным состоянием», которое поддерживает путешествие во времени? - Это ли не показатель отсталости десктопных операционных систем! Могут ли старые Mac и Windows путешествовать во времени так же изящно, как наши веб-страницы на основе Redux?

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

Конечно, Redux решает болевую точку, а именно проблему обмена состояниями между глубоко вложенными компонентами. Но решение этой проблемы не означает, что мы должны вывести государство на глобальный уровень. Воплощение этой проблемы можно просто понять как:Метод, реализованный в компоненте A, событие, которое его запускает, находится в компоненте B, а компоненту C необходимо подписаться на результат выполнения...В настоящее время действительно сложно иметь дело с чистым React, но пока хранилище размещается на верхнем уровне одного из трех компонентов A, B и C, а не глобального, а затем через настройку Контекст, достаточно решить эту проблему.

Путешествие во времени и шаблон

С другой стороны, Redux часто критикуют за то, что в нем много шаблонного кода: чтобы отправить простой запрос, вам нужно пройти через Action, Reducer и Middleware, а нагрузка на обдумывание относительно велика. Эта деталь на самом деле имеет тонкую связь с принципом реализации путешествия во времени, Проще говоря, ее можно понимать как Redux.Жертвовать опытом разработки ради опыта отладки:

В своем выступлении Дэн Абрамов упомянул о важной возможности, которую дает комбинация Webpack HMR и Redux DevTools: как только вы измените код Reducer, все действия будут переоценены, и их состояние будет обновлено.

Мы можем понимать гранулярность HMR как горячую замену на функциональном уровне (понимание автора здесь все еще неглубоко, пожалуйста, не забудьте указать на ошибки и упущения), а минимальная гранулярность для реализации логики управления состоянием в Redux оказывается чистой работает как Редуктор. Поэтому для самого Дэна реализация фичи «пока обнаруживается, что функция пропатчена, перезапускать все действия в формате JSON» на архитектуре Redux не требует никаких хитрых операций.Да — так он реализовал Redux DevTools в в неделю, и это действительно сильно! Цена в настоящее время такова: разработчики, использующие Redux, должны использовать этот сверхмощный механизм на этапе разработки, чтобы Дэн мог легко улучшить процесс отладки... В технических компромиссах нет абсолютно правильных или неправильных решений, и стоимость разработки и отладки является компромиссом, здесь без комментариев.

Путешествие во времени не работает из коробки

Помимо некоторых проблем, вызванных поддержкой Redux путешествий во времени, в этой идее кроется еще одна невидимая ямка: «Redux DevTools очень хорошо поддерживает путешествия во времени, поэтому интегрировать эту функцию в мое приложение не составит труда. 』Как упоминалось ранее, при реализацииуправляемый событиямиФункция путешествия во времени действительно важна, когда речь идет о интерфейсном приложении. Но сложность реализации этой функции, вероятно, не та, которую можно легко решить, подключив Redux. Здесь мы берем управляемое событиями приложение для редактирования форматированного текста в качестве примера, чтобы перечислить несколько конкретных примеров, встречающихся в бизнесе:

  • При использовании Slate.js стек отмены в некоторых случаях случайно очищался. Прочитав исходный код, мы обнаружили, что реализация стека отмен в то время заталкивала изменения, когда редактор был инициализирован как первый элемент стека. При попытке отменить это побочный эффект случайно нарушил бы логику подсчета редактора, что привело бы к потере контента, который должен был быть переделан обратно. Мы исправили эту ошибку с помощью PR, но в стеке отзывов осталось много похожих деталей.
  • В некоторых бизнес-сценариях трудно решить основные операции со стеком, такие как push и pop при отмене и повторном выполнении. Например, в процессе загрузки изображения пользователь по-прежнему может вводить текст. В это время операция события отмены «изменение хода выполнения» будет «смешиваться» с событием ввода пользователя в стеке отмены, что делает его сложнее отменить.
  • Для входных событий, которые происходят постоянно, требуется другая обработка дедупликации. Например, если пользователь непрерывно вводит строку текста, при отмене действия всю строку необходимо отменить за один раз, а если пользователь вводит медленно слово за словом, отмену следует выполнять слово за словом.

В этих сценариях решение для каждого случая имеет мало общего с идеей Redux. Для некоторых более сложных сценариев (таких как совместное редактирование форматированного текста в режиме реального времени) основой для реализации путешествия во времени больше не является простой стек отмены + полная замена состояния, а уже используется OT и т. д. Расширенные алгоритмы с меньшим количеством документов . Таким образом, в приложениях, управляемых событиями, если вам нужно реализовать функции типа путешествия во времени, есть два препятствия:

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

Таким образом, вопрос здесь ироничен: в приложениях, требующих функций путешествия во времени, Redux мало чем помогает, кроме введения своего набора соглашений. В сочетании с приведенным выше обсуждением вы можете видеть, что для путешествий во времени его в принципе не нужно реализовывать в приложениях, управляемых данными, а при реализации в приложениях, управляемых событиями, помощь Redux очень ограничена...

Какие у нас есть альтернативы?

Этот пост не для продажи нового колеса, но для обоих вышеперечисленных сценариев мы обнаружили, что есть более подходящие варианты управления состоянием. MobX и RxJS — это две библиотеки, которые я предпочитал раньше, и после повторного изучения сцены я обнаружу, что у них есть свои сильные стороны:

MobX и приложения, управляемые данными

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

  • Структура модели данных на основе классов может легко инкапсулировать операции добавления, запроса и удаления каждой модели. И очень удобно создавать несколько экземпляров разных хранилищ и внедрять их в нужные компоненты. Для связи между магазинами добавьте ссылку на RootStore при создании экземпляра дочернего хранилища.
  • Объявления типов на основе TS гораздо более продвинуты, чем необработанные строковые константы + необработанные объекты JS в Redux.
  • Механизм обновления, основанный на отслеживании зависимостей, может точно обновлять компоненты по требованию при обновлении свойства объекта. В общих бизнес-сценариях это лучше, чем операция полного изменения состояния и последующего сравнения. Для справки, в сцене с большим количеством перерисовок оптимизированная реализация Redux от Дэна Абрамова фактически достигла уровня MobX из коробки.

Следует отметить, что преимущество MobX в производительности при перерисовке достигается за счет большего объема памяти после перехвата доступа. Что касается этого компромисса, автору удалось проконсультироваться с лектором по разработке ядра UC, который поделился веб-оптимизацией на D2 о влиянии использования памяти на производительность внешнего интерфейса. Согласно ответу dalao, основными кейсами в этом отношении по-прежнему являются очевидные антипаттерны, такие как загрузка большого количества изображений, а потребление памяти моделью данных при управлении состоянием не является узким местом, влияющим на производительность. С этой точки зрения компромиссы дизайна и компромиссы MobX можно считать целесообразными.

RxJS и приложения, управляемые событиями

В интерфейсных приложениях, управляемых событиями, понимание асинхронной логики очень важно. В этой связи,redux-sagaКласс библиотек предоставляет некоторые способы обработки асинхронных побочных эффектов, но если вы понимаете RXJS, вы обнаружите кажущиеся мощными возможности Saga перед моделью мышления потока событий RX, это игрушка.

Если вы понимаете RxJS с точки зрения приложений, управляемых данными, вы только почувствуете, что его API очень тяжелый и навязчивый. На самом деле вам нужно почувствовать мощь этого набора концепций в сценарии, управляемом событиями. Примером здесь может служить метод планирования лифта при ожидании лифта каждый день: состояние лифта напрямую определяется потоком событий нажатия пользователем кнопки этажа, в это время реактивное программирование RxJS может разумно моделировать этот бизнес . В качестве учебника по изучению RxJS на примерах автор написал статью ранее«Введение в реактивное программирование: реализация симулятора планирования работы лифта»колонна и поддерживающаяDemoОна реализована, и заинтересованные студенты могут ее прочитать.

Суммировать

Нет никаких сомнений в том, что путешествие во времени является мощным средством отладки. В этой статье обсуждаются некоторые проблемы, которые могут возникнуть при внедрении путешествий во времени от инструментов отладки к бизнесу: интерфейсные приложения, управляемые данными, мало востребованы; функции путешествий во времени в Redux привносят некоторые антипаттерны; реализация путешествий во времени Есть и другие технические детали иметь дело с временами, которые выходят далеко за рамки того, с чем может справиться Redux, и т. д. В качестве альтернативы инструмент управления состоянием на основе объектно-ориентированного программирования MobX и основанный на реактивном программировании RxJS являются фаворитами автора в различных сценариях. Что касается новых колес, которые не рассматриваются в GraphQL и других текстах, я надеюсь, что читатели с соответствующим опытом, dalao, могут дать мне несколько советов.

Эта статья вроде как нацелена на Redux везде, хотя здесь действительно есть какие-то интересы (он мне всегда не очень нравится, и использование его не такое глубокое, как MobX, RxJS или даже Vuex), но выводы в статья подкреплена реальными сценариями. , абсолютно нетRedux API 好难学所以它肯定很烂такая идея. И работа команды Redux тоже заслуживает уважения. Если в статье есть какие-то отклонения в понимании Redux и путешествий во времени, надеюсь, читатели укажут на это, и я также очень готов пересмотреть и оптимизировать свои идеи на основе обсуждения.

Последний элемент личного блага — это понимание автором внешнего «круга»: я лично обнаружил, что многие люди в этой области слепо поклоняются фреймворкам и инструментам, которые они используют ежедневно: другим не разрешено прокомментируйте используемый ими фреймворк; проблема дизайна фреймворка объясняется как метафизическая проблема «вы непросты в использовании, потому что вы недостаточно квалифицированы»; прямое обозначение подобных инструментов как «нехороших»… Может быть, это действительно отражает определенную «настойчивость и любовь» к интерфейсу, но это также делает атмосферу обсуждения в местном сообществе очень плохой по сравнению с зарубежными странами. Один из открытых вопросов, которые я люблю задавать во время интервью, звучит так: «Что не так с предпочитаемым вами фреймворком? », этот вопрос является не только дискриминационным (многие кандидаты с посредственными показателями часто отвечают «Я не думаю, что что-то не так», чтобы продемонстрировать свое знакомство с фреймворком…), но и думать в противоположном направлении на самом деле полезнее. для нас, чтобы в сочетании с реальными сценариями понять принципы и компромиссы проектирования фреймворка.

Спасибо, что настояли на том, чтобы увидеть здесь, я надеюсь, что эта статья поможет вам~