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

Проблема: смешать яблоки с апельсинами

Создание полнофункциональных приложений с использованием JavaScript в 2019 году часто требует интеграции как минимум 3 или 4 частей:

  • фреймворк для управления вашими визуальными компонентами, например Реагировать;
  • фреймворк для управления состоянием вашего приложения, например Redux;
  • бэкэнд-решение для ваших данных и аутентификации, например Firebase; а также
  • если вам нравится обнаруживать ошибки во время компиляции, система типов, такая как TypeScript

Было предложено несколько решений для управления большей частью вышеуказанной комбинации, например, библиотека react-redux-firebase на GitHub. Но после 700+ коммитов от 80+ участников (и даже после столь долгожданного внедрения React Hooks) этой библиотеке еще не удалось обеспечить действительно простой и интуитивно понятный интерфейс для разработчиков, и мы не думаем, что это возможно.

Суть проблемы, на наш взгляд, заключается в том, что стек React / Redux / Firebase всегда будет смешивать очень разные шаблоны и вычислительные парадигмы для манипулирования данными:

  • 🍏 React обрабатывает данные с помощью синхронных функций, которые обычно оцениваются несколько раз по мере загрузки и изменения данных,
  • 🍊Redux не позволяет вам просто вызывать функции, но вместо этого заставляет вас отправлять действия, которые изменяют данные с помощью редукторов, которые обычно оцениваются один раз.
  • 🍏 Поскольку само сокращение не имеет встроенной поддержки асинхронных эффектов, вы, вероятно, познакомитесь с концепцией преобразователей (с преобразователями-преобразователями) или сагов (с редукторами). -саги)
  • 🍊Или вы могли заменить redux альтернативой, таким образом введя еще больше концепций, таких как наблюдаемые (например, MobX) или потоки (например, Rx).
  • 🍏 Взаимодействие с Firebase зависит от асинхронных функций, а данные в реальном времени из Firestore обновляются несколько раз с использованием подписок.
  • 🍊React Hooks можно затем использовать, чтобы попытаться упростить многое из вышеперечисленного, но они полагаются на еще одну парадигму. Хуки интуитивно представляют собой форму внедрения зависимостей и обычно обрабатывают асинхронный характер побочных эффектов с помощью функций обратного вызова.

Это огромный технический беспорядок, и это очень типичный пример «усталости фреймворка», которая в мире JavaScript становится только хуже. Это не интуитивно понятный способ создания приложений, и он заставляет новичков сталкиваться с крутой кривой обучения. И это также реальная проблема для экспертов; написание кода занимает больше времени из-за шаблона, проверка кода занимает больше времени из-за общей неудобства и непоследовательности, а написание тестов очень громоздко, а это означает, что вы, вероятно, не напишете так много. И вдобавок ко всему, получение полезной обратной связи от TypeScript безнадежно, потому что большая часть данных, поступающих из Firestore, не будет введена.

Решение: переосмысление государственного управления

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

  1. Предоставляет (большинство) те же преимущества, что и Redux, без шаблонов и ненужных абстракций (для этого мы обычно полагаемся на прокси и / или транспиляцию).
  2. Сильно упростите вычислительную модель, чтобы она соответствовала React (на основе синхронных функций, оцениваемых несколько раз).
  3. Разрешить пользователям интегрировать серверные технологии, такие как Firebase, путем добавления «плагина» (в отличие от системы промежуточного программного обеспечения Redux, которая является очень низкоуровневой).
  4. Включите максимальный вывод TypeScript по дизайну (это означает, что вы определяете свою схему один раз, а затем TypeScript выводит все ваши типы, не требуя ручных аннотаций).

Замена Redux - довольно амбициозная задача: мы говорим о библиотеке с более чем 50 тысячами звезд на GitHub и более чем 3 миллионами загрузок в неделю на npm. И мы также не первые разработчики, заинтересованные в замене Redux (мы нашли около 100 связанных библиотек на GitHub). Но мы не нашли ни одной попытки, которая следовала бы тем же приоритетам.

Вы также могли столкнуться с десятками сообщений в блогах «Redux is dead» на Medium за последние несколько месяцев, часто приводя аргументы, что GraphQL и / или React Hooks могут помочь вам избавиться от Redux. Но мы не нашли реальных доказательств этого, кроме игрушечных примеров приложений. Хуки только обеспечивают более удобный синтаксис для работы с состоянием React или контекстом React, но они не сильно изменили управление состоянием. Ни Hooks, ни GraphQL по-настоящему не заменили Redux - они только позволяют уменьшить количество Redux, необходимое в вашем приложении. Вот почему мы считаем, что управление государством остается проблемой, в которую стоит разобраться.

Покажи мне код!

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: мы все еще экспериментируем с различными вариантами и приглашаем вас проверить наше репо, чтобы увидеть, как наша структура выглядит сегодня. Но мы также хотели бы получить ваши отзывы (о Slack или в комментариях под этой статьей) о синтаксисе и архитектуре, которые мы планируем в долгосрочной перспективе, которые описаны ниже.

Шаг 1. Определите свои модели данных

Prodo предоставит вам функцию dataModel, параметризованную определением типа и списком плагинов. Затем вы сможете экспортировать такие переменные, как state (состояние редукции вашего приложения), auth (данные аутентификации, автоматически извлекаемые из Firebase) и db (автоматически синхронизируемые с Firestore), которые можно использовать непосредственно в определениях ваших действий. . Вы также сможете экспортировать ловушку под названием useData для использования (и отслеживания изменений) данных в ваших компонентах React.

Обратите внимание, что в приведенном выше описании мы не экспортируем определение типа, потому что оно нам не понадобится в других файлах. Это потому, что мы сможем сделать вывод обо всем по типам db, state, auth и useData.

Шаг 2. Определите свои действия

Действия Prodo будут определены как простые старые функции JavaScript, и эти функции смогут использовать операции мутации для изменения данных в вашем состоянии и вашей базе данных, как если бы они состояли из объектов JSON в памяти. Но это явно абстракция.

Под капотом мы будем использовать immer.js для реализации семантики копирования при записи и отслеживания изменений данных неразрушающим способом (что делает возможной отладку во времени). Мы также будем полагаться на динамическую привязку для сопоставления состояния и базы данных с текущим контекстом выполнения каждого действия. Интуитивно это означает, что выражение, такое как state.roomId, фактически будет оценивать window.currentProdoContext.state.foo. Затем эту глобальную переменную currentProdoContext нужно будет менять местами при запуске и завершении различных действий, и мы сможем сделать это надежным способом, пока действия остаются синхронными.

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

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

Шаг 3. Использование ваших данных и действий из React

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

Вот как будет выглядеть компонент сообщения:

Вот как будет выглядеть компонент RoomSelector (с управляемым вводом):

Вот как будет выглядеть компонент PostMessage:

Наконец, вот как наше приложение чата будет выглядеть в конце, используя функцию запроса из плагина Prodo для извлечения и просмотра коллекций из Firestore:

Потребуется изрядное количество нетривиальных эвристик, чтобы гарантировать, что вышеуказанные компоненты повторно визуализируются (эффективно) при обновлении данных, но эксперименты, которые мы провели до сих пор, предполагают, что мы сможем сопоставить Redux и MobX. выступления. Вы также можете заметить, что в приведенном выше примере - в отличие от большинства фреймворков - не требуется заключать каждый отдельный компонент в функции «соединения». Это снова то, чего мы смогли достичь в наших экспериментах (путем динамического переопределения функции createElement React для автоматического подключения всех соответствующих компонентов), но влияние на производительность также потребует надлежащего тестирования. И это лишь некоторые примеры захватывающих задач, которые мы решаем сейчас, чтобы обеспечить по-настоящему простой, свободный от шаблонов опыт для наших пользователей.

Что теперь?

Если вам понравилось то, что вы прочитали, или вы просто хотите посмотреть, к чему это идет, подумайте о том, чтобы пометить наш репозиторий на GitHub, чтобы мы знали, что вам не все равно. Вы также можете быть в курсе предстоящих функций и, что более важно, присоединиться к обсуждению, присоединившись к нашему Сообществу Slack.

Если вы хотите сразу приступить к работе, вы можете попробовать Prodo сегодня - наши документы находятся в сети по адресу https://docs.prodo.dev, и вы можете быстро создать свой собственный пример, следуя руководству здесь.