Руководство по Flux в картинках
Flux является одновременно одной из самых популярных и наименее понятных тем в современной веб-разработке. Это руководство — попытка объяснить его так, чтобы стало понятно каждому.
Проблема
Прежде всего, необходимо объяснить основную проблему, которую решает Flux. Сам по себе, Flux — это схема обработки данных в вашем приложении. И Flux, и React появились в Facebook. Многие используют их вместе, хотя это и необязательно. Они были разработаны для решения набора проблем, с которыми столкнулся Facebook.
Одним из хорошо известных примеров подобных проблем был баг уведомлений. Когда вы заходили на Facebook, вы могли видеть уведомление над иконкой сообщений. Кликнув по этой иконке, вы не обнаруживали никаких новых сообщений. Уведомление исчезало. Затем, после нескольких минут нахождения на сайте, уведомление снова появлялось. Вы кликали на иконку снова… но новых сообщений так и не было. Это могло продолжаться снова и снова, циклически.
Эта цикличность проявляла себя не только на стороне пользователей сайта. Она также существовала и у команды Facebook. Они исправляли ошибку, и всё было хорошо какое-то время, но затем она возвращалась. Так этот баг и ходил туда-сюда между состоянием “решено” и “новая задача”.
Facebook искал способ вырваться из этого порочного круга. Они не ставили перед собой цели просто исправить ошибку один раз. Они собирались сделать систему предсказуемой таким образом, чтобы убедиться, что эта проблема больше не всплывет на поверхность.
Основная проблема
Основной выявленной проблемой оказался “путь”, по которому “шел” поток данных в приложении.
Заметьте: вся информация почерпнута из упрощенного описания, которым они поделились в разговоре. Наверняка фактическая архитектура выглядела по-другому.
У них были модели, которые проводили данные и отправляли их на уровень представлений для отображения.
Так как взаимодействие с пользователем происходило через представления, эти представления иногда нуждались в обновлении моделей, базирующихся на введенных пользователем данных. А иногда модели обновляли другие модели.
Кроме того, иногда подобные действия вызывали каскад других изменений. Я представляю это как увлекательную аркадную игру Понг — сложно понять, куда мячик приземлится (или закатится за экран).
Добавьте еще тот факт, что эти изменения могли случаться асинхронно. Одно изменение могло вызвать множество других. Я представляю это как бросание полного мешка мячиков для пинг-понга в игру, когда они разлетаются во всех направлениях с пересекающимися траекториями.
В конце концов, это затрудняет отлаживание потока данных.
Решение: однонаправленный поток данных
Так, Facebook решил попробовать другой тип архитектуры, где данные “текут” в одном направлении — только в одном! — и когда вам нужно вставить новые данные, поток начинает все с самого начала. Они назвали эту архитектуру Flux.
На самом деле, это очень круто… но возможно, вы не скажете это, увидев представленную диаграмму.
Как только вы поймете Flux, эта диаграмма окажется предельно ясной. Проблема заключается в том, что без знания документации Flux, не думаю, что эта диаграмма поможет вам понять хоть что-то… и это именно то, что диаграмма должна делать! Она должна лишь дать вам общую картину для понимания системы прежде чем вы c головой погрузитесь в разработку специфичных вещей.
Что помогло мне лучше понять Flux? Точно не диаграмма вроде этой. Вместо этого можно думать о системе в терминах разных персонажей, работающих вместе, как команда, собирающаяся достигнуть цели. Итак, представляю вам персонажей, о которых идёт речь.
Встречайте персонажей
Я собираюсь быстро познакомить вас с ними прежде чем объяснять их взаимодействие.
Создатель действия
Судя по названию, он отвечает за создание действий, т.е. “пути”, по которому должны пройти все изменения и взаимодействия. Когда бы вы ни захотели изменить состояние приложения или изменить представление данных, вы должны вызвать действие.
Я думаю о создателе действия, как о телеграфисте. Вы идете к создателю действия, зная только, какое сообщение хотите послать, а затем создатель действия форматирует его понятным для остальной системы образом.
Он создает действие с типом и полезной нагрузкой. Тип — один из тех, которые вы определили в вашей системе (обычно — список констант). Пример такого действия будет чем-то вроде MESSAGE_CREATE или MESSAGE_READ.
Есть приятное “побочное действие” у этой части системы, которая знает все возможные действия. Новый разработчик может прийти на проект, открыть файлы создателя действия и увидеть весь API — т.е. возможные изменения состояния системы.
Диспетчер
Диспетчер, по сути, — это реестр обратных вызовов. Он чем-то напоминает телефонного оператора, работающего с коммутатором. Он хранит список всех хранилищ, которым нужно отправлять действия. Как только действие приходит от его создателя, диспетчер отправляет его по разным хранилищам.
Это происходит синхронно, что помогает в эффекте игры Понг со многими шарами, о котором говорилось выше. И если вам нужно установить зависимости между хранилищами (чтобы одно обновлялось раньше другого), вы можете указать диспетчеру управлять ими с помощью waitFor().
Диспетчер Flux отличается от диспетчеров остальных архитектур. Действие отправляется во все зарегистрированные хранилища независимо от типа действия. Это значит, что хранилище не просто подписывается на действие, оно “слышит” обо всех действиях и фильтрует, о чем стоит позаботиться, а о чем — нет.
Хранилище
Следующее — это хранилище. Оно содержит все состояния приложения, а также всю логику изменений состояния.
Я думаю о хранилище, как о вышестоящем бюрократе. Все изменения состояний должны быть сделаны им лично. И вы не можете напрямую запросить, чтобы состояние изменилось. В хранилище нет такого “приспособления”. Чтобы запросить изменение состояния, вы должны пройти надлежащую процедуру… т.е. подтвердить действие через связку “создатель действия/диспетчер”.
Как было упомянуто выше, если хранилище зарегистрировано вместе с диспетчером, то ему будут отправлены все действия. Внутри хранилища обычно находится переключатель состояния, который анализирует тип действия и решает, нужно ли хранилищу реагировать на него. Если хранилище должно отреагировать на действие, оно поймет, какие именно изменения нужно сделать, основываясь на этом действии, и обновит состояние.
Как только хранилище сделало изменение состояния, оно вызовет событие изменения. Это уведомит контроллер о том, что состояние было изменено.
Контроллер и представление
Представления в ответе за получение состояния и отображение его для пользователя так же, как и за принятие пользовательских данных.
Представление — это докладчик. Он не беспокоится ни о чем в приложении, он просто знает, что есть поступившие ему данные, и как их форматировать для понятного человеку вывода (используя HTML).
Контроллер похож на менеджера среднего звена между хранилищем и представлением. Хранилище говорит ему, когда состояния изменилось. Контроллер собирает новое состояние и затем отправляет обновленное состояние по всем связанным представлениям.
Как они работают вместе
Давайте посмотрим, как все эти персонажи взаимодействуют.
Настройка
Сначала необходима настройка: инициализация приложения, которая происходит лишь однажды.
1. Хранилища дают диспетчеру знать, что они хотят быть уведомлены о произошедшем действии.
2. Затем контроллер запрашивает у хранилища последнее состояние.
3. После того, как хранилища передают состояние контроллеру, они также передают его своим дочерним представлениям для отображения.
4. Контроллер просит хранилища держать его в курсе, когда состояние изменяется.
Поток данных
Как только настройка окончена, приложение готово принимать пользовательские данные. Так что давайте вызовем действие с помощью пользователя, сделавшего изменение.
Мы запустим поток данных через действие пользователя.
1. Представление говорит создателю действия приготовить его.
2. Создатель действия форматирует его и отправляет диспетчеру.
3. Диспетчер отправляет действие хранилищам последовательно. Каждое хранилище уведомляется обо всех действиях. Потом оно решает, иметь с ним дело или нет, и соответственно изменяет состояние.
4. Как только состояние изменилось, хранилище дает об этом знать всем подписанным на него контроллерам.
5. Эти контроллеры затем запросят у хранилища обновленное состояние.
6. После того как хранилище отдает состояние, контроллер передаст его дочерним представлениям запрос на повторное отображение согласно новому состоянию.