Мы потратили последние пару лет на создание множества приложений с использованием 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, не будет введена.
Решение: переосмысление государственного управления
Мы считаем, что проблема несовместимых шаблонов и парадигм может быть решена только путем изменения слоя, который связывает все вместе: уровня управления состоянием. Большая простота может быть достигнута путем переосмысления управления состоянием с учетом правильных приоритетов. В частности, мы считаем, что хорошее решение должно:
- Предоставляет (большинство) те же преимущества, что и Redux, без шаблонов и ненужных абстракций (для этого мы обычно полагаемся на прокси и / или транспиляцию).
- Сильно упростите вычислительную модель, чтобы она соответствовала React (на основе синхронных функций, оцениваемых несколько раз).
- Разрешить пользователям интегрировать серверные технологии, такие как Firebase, путем добавления «плагина» (в отличие от системы промежуточного программного обеспечения Redux, которая является очень низкоуровневой).
- Включите максимальный вывод 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, и вы можете быстро создать свой собственный пример, следуя руководству здесь.