Это пост о чудесах совместного использования React и Firebase. В первой половине я говорю об архитектуре, а во второй - в коде.

Наслаждаться!

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

  1. Реагировать. Все дело в упрощенной ментальной модели. Когда мне кажется, что я начинаю принимать React как должное, я снова читаю эту страницу.
  2. Firebase. Мой роман с Firebase только зарождается, но очень силен. Работать с ним так быстро, и, как мы увидим, как только вы поймете, как он сочетается с React, эта простая ментальная модель распространяется от внешнего интерфейса к серверному и ко всем частям между ними.

Но увы, это не все щенки и радуги.

Firebase - это странно.

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

Совет от профессионала: я написал весь свой код Firebase для воображаемого приложения с воображаемой структурой данных. Я чувствую, что это помогло освободиться от оков всего, что я уже знал о моем собственном приложении. Таким образом, фрагменты кода в этом посте, в которых говорится о «школах», «курсах» и «студентах», были фактически написаны до того, как я пошел и включил их в свое приложение, и все они были написаны в виде фрагмента в инструментах разработчика Chrome.

Архитектура приложения

Несомненно, два самых сексуальных слова в английском языке.

Я скоро перейду к материалам Firebase, но сначала вот как может выглядеть приложение без Firebase:

А теперь давайте привяжем к этому человека…

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

Это выглядит примерно так:

  1. Пользователь нажимает на «показать справку». Компонент отправляет действие с типом SHOW_MODAL и значением help (действие создается actionCreators.js).
  2. Отправленное действие обновляет магазин.
  3. В другом месте терпеливо ждал компонент HelpModal. Паукообразное чувство модального окна справки пощипывает, когда магазин обновляется, он запускает свой метод рендеринга и видит, что значение visibleModal теперь равно help. Он очень рад, что ему больше не нужно возвращать null, поэтому он отображает некоторый DOM и бум, пользователь видит модальную помощь.
  4. Наш пользователь читает справку (потому что это делают пользователи), а затем щелкает мышью. Это событие клика снова отправляет SHOW_MODAL, на этот раз передавая null. Это устанавливает для visibleModal значение null, и плохой HelpModal снова не может нам предложить и уходит в тень.

Это основы, надеюсь, вы быстро прочитали.

Но что, если мы хотим сохранить какие-то данные? О, у меня есть идея, давайте воспользуемся Firebase!

Итак, что такое Firebase?

Отличный вопрос. Замечательный вопрос.

Это большая старая база данных в небе. Вы отправляете запросы на добавление / обновление / удаление через Firebase SDK, который запускается в браузере. Вы также отслеживаете изменения в данных и делаете что-то, когда они меняются.

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

Давайте посмотрим, как выглядит наше приложение с использованием Firebase.

Вроде как космический корабль. Или гоночную машину.

Теперь давайте рассмотрим пример взаимодействия с данными, которые мы хотим сохранить.

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

  1. Пользователь нажимает кнопку «добавить учащегося», и компонент вызывает функцию с именем addStudent.
  2. Функция addStudent отправляет сообщение в Firebase, предлагая добавить студента в список студентов.
  3. Firebase обновляет данные и генерирует событие изменения.
  4. Слушатель для "студент добавлен" реагирует на изменение Firebase и отправляет сведения о новом учащемся в магазин.
  5. Магазин обновляется и выдает изменение.
  6. Компонент повторно визуализируется, и новый учащийся отображается на экране.

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

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

Все правильные вопросы, и именно здесь на помощь приходит странная Firebase.

На втором шаге я отправляю сообщение в Firebase, чтобы добавить студента. И я сказал вам, что Firebase находится в небе, так что вы, вероятно, думаете, что это займет несколько сотен миллисекунд. Но технически я отправляю его в Firebase SDK, а Fireabse SDK имеет больше, чем просто договоренность типа pub / sub с большой базой данных в небе. Фактически это локальная копия (некоторой) этой базы данных.

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

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

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

  • firebaseWatcher.js Видите ли, все в жопе, мне нужно начать с нижнего правого угла! Когда приложение загружается впервые, я настраиваю группу слушателей. Если что-то добавляется или изменяется в базе данных, этот отдельный объект инициирует событие изменения, и соответствующее действие будет отправлено в хранилище.
  • Store Все эти прослушиватели в firebaseWatcher.js срабатывают, когда есть изменение, но также и при первом связывании, поэтому он заполняет наше хранилище при загрузке страницы (или когда пользователь входит в систему).
  • Компонент. Здесь обычный тариф. Многие компоненты без состояния просто превращают хранилище в пиксели и ждут, пока пользователь что-то сделает. Наконец пользователь решает добавить нового учащегося в систему, и компонент переходит в состояние готовности и вызывает действие…
  • actions.js «действие» в данном случае - это просто функция, которая что-то делает с переданными данными. Обычно отслеживание события для аналитики и передача данных…
  • firebaseActions.js. Он обрабатывает все сообщения Firebase. И, очевидно, очень похоже на меня, не обращает внимания на какой-либо ответ.
    Данные в Firebase нормализованы (как и хранилище), поэтому этот файл играет роль ORM, что означает, что он умеренно сложен, но у него есть только одно задание: отправить данные в Firebase.
    В Firebase есть еще одна волшебная вещь: удалить учащегося из базы данных и любые ссылки на него в курсах и школах за одну операцию очень легко. Легко говорю вам.
  • И мы вернулись к началу. Firebase генерирует событие, firebaseWatcher.js отправляет действие в хранилище, которое, в свою очередь, генерирует событие изменения, а компоненты реагируют и повторно визуализируют себя.

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

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

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

ПОКАЖИТЕ КОД!

Если вы знаете React и Firebase, то, вероятно, до сих пор спали, так что давайте испачкаемся и посмотрим на код.

Если вы не знакомы с ES6, вам нужно пойти и изучить ES6.

(Структура данных, упомянутая во всем этом коде, находится внизу страницы.)

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

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

Я слышал, вы сказали, что это превосходно, но Я ХОЧУ БОЛЬШЕ КОДА.

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

Если вы знаете ImmutableJs, это часть setIn () и часть withMutations (). То есть вы можете писать во многие ветви структуры данных за один атомарный вызов update (). Вы просто отправляете объект с парами опора / значение, где опора - это путь к месту, где вы хотите установить значение. Сама ценность может быть глубоким объектом.

Объедините эту логику с синтаксическими полезностями ES6, такими как вычисляемые имена свойств и литералы шаблонов, и вы получите забавный, но великолепный синтаксис, который вы видите в строке 63 ниже.

Еще одна странность Firebase: у меня есть уникальные ключи объектов, которые я создаю до. Я отправляю их в базу данных, используя пустой push (), который возвращает ключ, который новая вещь будет иметь.

Bonkers.

Теперь давайте удалим кое-что. Вы можете вызвать метод remove () для элемента в базе данных, но если у вас есть другие вещи, относящиеся к этому объекту, вам также необходимо очистить их, и если вам не нравится, что происходит ужасно неправильно, в далеком будущем вы захотите сделать это за одну операцию. Поэтому мы воспользуемся нашим хорошим другом update () и передадим несколько нулей, что даст тот же конечный результат.

Сценарий: Бобби Браун сдался, и мы хотим удалить его из базы данных.

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

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

Итак, как видите, мы генерируем объект removeData побитно, но отправляем его в db.update () за один раз, поэтому удаление по-прежнему происходит атомарно.

В общем, не так много кода для довольно сложной операции.

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

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

Как и было обещано, вот структура данных, с которыми мы работали.