Нам в Хекслете нравится ReactJS и Flux. Нам кажется, что это правильное направления развития. Мы любим функциональное программирование и чистые функции, и когда сложные архитектуры упрощаются за счет подходов, связанных с ними — это круто. По Реакту уже есть немало ресурсов в интернете, в том числе наш практический курс по React JS. Последний урок в этом курсе называется «Однонаправленное распространение данных», и там мы подходим к интересной теме, которая лежит в основе архитектуры Flux.
Flux это паттерн распространения даных в приложении. Flux и React выросли в стенах Фейсбука вместе. Многие используют их одновременно, но ничто не мешает использовать их по отдельности. Они были созданы для решения конкретной проблемы в Фейсбуке.
Мы используем React и Flux в своей браузерной среде разработки Hexlet IDE (она в опен-сорсе), в которой учащиеся выполняют практические задания. Flux одновременно очень популярен и очень непонятен для многих в мире веба. Сегодняшний перевод — попытка объяснить Flux на пальцах (ну, то есть картинках).
Вначале нужно понять, какую проблему решает Flux.
Известный пример — баг с нотификациями. Вы заходите в Фейсбук, видите нотификацию у иконки сообщений. Кликаете, но нового сообщения нет. Нотификация пропадает. Потом, через несколько минут взаимодействия с сайтом, нотификация возвращается. Кликаете снова… и снова нет сообщений. И так снова и снова и снова.
Это был не просто циклический баг для юзера. Это была циклическая проблема в команде Фейсбука. Они чинили его, и на время баг исчезал, но потом вновь появлялся, и так снова и снова: issue → fixed → issue → fixed…
Они искали способ решить проблему раз и навсегда. Не просто исправить баг, а сделать так, чтобы система не позволяла таким багам появляться.
Фундаментальная проблема крылась в механизме движения данных внутри приложений.
Есть модели, содержащие данные, и они передают данные в слой, который рендерит данные.
Юзер взаимодействует с видом (views), поэтому виду иногда нужно обновлять модель на основе пользовательского ввода. И иногда моделям нужно обновлять другие модели.
К тому же, экшны иногда генерируют каскадные изменения. Я представляю все это как напряженную игру в Pong: сложно понять, куда полетит мячик (и не свалится ли за пределы экрана вообще).
Добавьте к этому возможные асинхронные изменения. Одно изменение может генерировать несколько других. Это как выкинуть кучу шариков для настольного тенниса на экран Pong'а, так чтобы они летали во все стороны по пересекающимся траекториям.
В общем, этот поток данных хрен отладишь.
Фейсбук решил попробовать другую архитектуру, в которой данные двигаются всегда в одном и только одном направлении. И когда нужно добавить новые данные, поток начинается с самого начала. Назвали эту архитекуру Flux.
Это в реальности крутая штука… но по картинке выше, наверное, не заметно.
После того, как разберетесь с Flux, картинка станет понятнее. Проблема в том, что если взяться за изучение документации Flux с нуля, такая иллюстрация не помогает понять суть. Мне она не помогла. Зато помог другой подход: я начал думать о системе как о наборе персонажей, которые работают вместе чтобы добиться результата. Так что вот, знакомьтесь, это мои вымышленные друзья.
Сначала представлю всех по-быстрому.
Первый персонаж — создатель действия. Он генерирует действия (экшены), и через него проходят все изменения и взаимодействия с системой. Хотите изменить состояние приложения или хотите изменить вид — нужно создать действие.
Я представляю его как оператора на телеграфе. Вы знаете, какое сообщение нужно послать, а создатель действия форматирует его таким образом, чтобы другие части системы поняли.
У действия есть тип и груз. Тип это один из заданных в системе типов (обычно, это список констант). Например, MESSAGE_CREATE или MESSAGE_READ.
Получается, часть системы знает о всех возможных действиях. У этого факта есть полезный побочный эффект. Новый разработчик может открыть файлы создателя действий и увидеть весь API — все возможные изменения состояния в системе.
Созданное действие передается диспетчеру.
Грубо говоря, диспетчер это большой регистр функций обратного вызова (коллбэков, callbacks). Что-то вроде оператора на телефонной станции. У него есть список всех хранилищ, которым нужно отправлять сообщения. Когда от создателя действия приходит новое действие, диспетчер передает его нужному хранилищу.
Он делает это синхронно, что упрощает ситуацию со множеством шариков в игре Pong, о которой я говорил выше. И если нужно задать зависимости между хранилищами (так, чтобы одно хранилище обновлялось перед другим), в диспетчере можно использовать waitFor().
Диспетчер Flux отличается от диспетчеров в других архитектурах. Действие отправляется всем зарегистрированным хранилищам вне зависимости от типа действия. Это означает, что хранилище не просто подписывается на определенные действия. Оно узнает обо всех действиях и фильтрует те, которые имеют для него смысл.
Хранилище содержит состояние приложения, и вся логика изменения состояния живет внутри.
Я представляю себе хранилище в виде дотошного бюрократа, контроллирующего больше, чем надо. Все изменения должны быть выполнены им лично. И нельзя напрямую запросить изменение состояния. У хранилища нет сеттеров (setters). Чтобы запросить изменение состояния, нужно следовать особой процедуре… нужнонаписать заявление на имя директора подать действие через канал создателя действия и диспетчера.
Как было сказано выше, если хранилище зарегистрировано у диспетчера, ему будут посылаться все действия. Внутри хранилища обычно есть switch, который проверяет тип действия и принимает решение реагировать на него или нет. Если хранилище реагирует на действия, то ему нужно произвести изменения на его основе и обновить состояние.
После изменения хранилище генерирует событие об изменении. Контроллер-вид (controller view) узнает об изменении состояния.
Виды (views) отвечают за рендер состояния для пользователя, а также за прием ввода от пользователя.
Вид это presenter. Он не знает ни о чем в приложении, он знает лишь о данных, которые ему переданы, и как форматировать эти данные чтобы людям было понятно (с помощью HTML).
Контроллер-вид (controller view) это некий менеджер среднего звена или агент между хранилищем и видом. Хранилище сообщает ему об изменении состояния. Он получает новое состояние и передает его всем своим видам.
Вначале — организация. Инициализация приложения происходит только один раз.
1. Хранилища сообщают диспетчеру о том, что хотят получать уведомления о действиях.
2. Контроллер-вид запрашивает у хранилищ их последние состояния.
3. Когда хранилища отвечают, контроллер-виды передают эти состояния своим дочерним видам на рендер.
4. Контроллеры вида просят хранилища сообщать им об изменениях состояния.
Когда инициализация завершена, приложение готово принимать ввод от пользователя. Давайте сгенерируем действие, как будто пользователь произвел изменение.
1. Вид говорит создателю действий чтобы тот подготовил действие.
2. Создатель действия форматирует действие и посылает его диспетчеру.
3. Диспетчер посылает действие хранилищам по порядку. Каждое хранилище получает сообщение о всех действиях. Потом оно решает реагировать на действие или нет, и обновляет (или не обновляет) состояние соответственно.
4. После изменения состояния, хранилище оповещает контроллеры вида, которые подписаны на него.
5. Эти контроллеры вида просят у хранилища дать им обновленное состояние.
6. После того, как хранилище передаст свое состояние, контроллер вида попросит своих дочерние виды перерендериться в соответствии с новым состоянием.
Вот так я представляю себе работу Flux'а. Надеюсь, вам это тоже поможет!
Ресурсы
Flux это паттерн распространения даных в приложении. Flux и React выросли в стенах Фейсбука вместе. Многие используют их одновременно, но ничто не мешает использовать их по отдельности. Они были созданы для решения конкретной проблемы в Фейсбуке.
Мы используем React и Flux в своей браузерной среде разработки Hexlet IDE (она в опен-сорсе), в которой учащиеся выполняют практические задания. Flux одновременно очень популярен и очень непонятен для многих в мире веба. Сегодняшний перевод — попытка объяснить Flux на пальцах (ну, то есть картинках).
Проблема
Вначале нужно понять, какую проблему решает Flux.
Известный пример — баг с нотификациями. Вы заходите в Фейсбук, видите нотификацию у иконки сообщений. Кликаете, но нового сообщения нет. Нотификация пропадает. Потом, через несколько минут взаимодействия с сайтом, нотификация возвращается. Кликаете снова… и снова нет сообщений. И так снова и снова и снова.
Это был не просто циклический баг для юзера. Это была циклическая проблема в команде Фейсбука. Они чинили его, и на время баг исчезал, но потом вновь появлялся, и так снова и снова: issue → fixed → issue → fixed…
Они искали способ решить проблему раз и навсегда. Не просто исправить баг, а сделать так, чтобы система не позволяла таким багам появляться.
Фундаментальная проблема
Фундаментальная проблема крылась в механизме движения данных внутри приложений.
Есть модели, содержащие данные, и они передают данные в слой, который рендерит данные.
Юзер взаимодействует с видом (views), поэтому виду иногда нужно обновлять модель на основе пользовательского ввода. И иногда моделям нужно обновлять другие модели.
К тому же, экшны иногда генерируют каскадные изменения. Я представляю все это как напряженную игру в Pong: сложно понять, куда полетит мячик (и не свалится ли за пределы экрана вообще).
Добавьте к этому возможные асинхронные изменения. Одно изменение может генерировать несколько других. Это как выкинуть кучу шариков для настольного тенниса на экран Pong'а, так чтобы они летали во все стороны по пересекающимся траекториям.
В общем, этот поток данных хрен отладишь.
Решение: однонаправленный поток данных
Фейсбук решил попробовать другую архитектуру, в которой данные двигаются всегда в одном и только одном направлении. И когда нужно добавить новые данные, поток начинается с самого начала. Назвали эту архитекуру Flux.
Это в реальности крутая штука… но по картинке выше, наверное, не заметно.
После того, как разберетесь с Flux, картинка станет понятнее. Проблема в том, что если взяться за изучение документации Flux с нуля, такая иллюстрация не помогает понять суть. Мне она не помогла. Зато помог другой подход: я начал думать о системе как о наборе персонажей, которые работают вместе чтобы добиться результата. Так что вот, знакомьтесь, это мои вымышленные друзья.
Знакомьтесь
Сначала представлю всех по-быстрому.
Создатель действия (action'а)
Первый персонаж — создатель действия. Он генерирует действия (экшены), и через него проходят все изменения и взаимодействия с системой. Хотите изменить состояние приложения или хотите изменить вид — нужно создать действие.
Я представляю его как оператора на телеграфе. Вы знаете, какое сообщение нужно послать, а создатель действия форматирует его таким образом, чтобы другие части системы поняли.
У действия есть тип и груз. Тип это один из заданных в системе типов (обычно, это список констант). Например, MESSAGE_CREATE или MESSAGE_READ.
Получается, часть системы знает о всех возможных действиях. У этого факта есть полезный побочный эффект. Новый разработчик может открыть файлы создателя действий и увидеть весь API — все возможные изменения состояния в системе.
Созданное действие передается диспетчеру.
Диспетчер
Грубо говоря, диспетчер это большой регистр функций обратного вызова (коллбэков, callbacks). Что-то вроде оператора на телефонной станции. У него есть список всех хранилищ, которым нужно отправлять сообщения. Когда от создателя действия приходит новое действие, диспетчер передает его нужному хранилищу.
Он делает это синхронно, что упрощает ситуацию со множеством шариков в игре Pong, о которой я говорил выше. И если нужно задать зависимости между хранилищами (так, чтобы одно хранилище обновлялось перед другим), в диспетчере можно использовать waitFor().
Диспетчер Flux отличается от диспетчеров в других архитектурах. Действие отправляется всем зарегистрированным хранилищам вне зависимости от типа действия. Это означает, что хранилище не просто подписывается на определенные действия. Оно узнает обо всех действиях и фильтрует те, которые имеют для него смысл.
Хранилище
Хранилище содержит состояние приложения, и вся логика изменения состояния живет внутри.
Я представляю себе хранилище в виде дотошного бюрократа, контроллирующего больше, чем надо. Все изменения должны быть выполнены им лично. И нельзя напрямую запросить изменение состояния. У хранилища нет сеттеров (setters). Чтобы запросить изменение состояния, нужно следовать особой процедуре… нужно
Как было сказано выше, если хранилище зарегистрировано у диспетчера, ему будут посылаться все действия. Внутри хранилища обычно есть switch, который проверяет тип действия и принимает решение реагировать на него или нет. Если хранилище реагирует на действия, то ему нужно произвести изменения на его основе и обновить состояние.
После изменения хранилище генерирует событие об изменении. Контроллер-вид (controller view) узнает об изменении состояния.
Controller view и view
Виды (views) отвечают за рендер состояния для пользователя, а также за прием ввода от пользователя.
Вид это presenter. Он не знает ни о чем в приложении, он знает лишь о данных, которые ему переданы, и как форматировать эти данные чтобы людям было понятно (с помощью HTML).
Контроллер-вид (controller view) это некий менеджер среднего звена или агент между хранилищем и видом. Хранилище сообщает ему об изменении состояния. Он получает новое состояние и передает его всем своим видам.
Как все они работают вместе
Организация
Вначале — организация. Инициализация приложения происходит только один раз.
1. Хранилища сообщают диспетчеру о том, что хотят получать уведомления о действиях.
2. Контроллер-вид запрашивает у хранилищ их последние состояния.
3. Когда хранилища отвечают, контроллер-виды передают эти состояния своим дочерним видам на рендер.
4. Контроллеры вида просят хранилища сообщать им об изменениях состояния.
Поток данных
Когда инициализация завершена, приложение готово принимать ввод от пользователя. Давайте сгенерируем действие, как будто пользователь произвел изменение.
1. Вид говорит создателю действий чтобы тот подготовил действие.
2. Создатель действия форматирует действие и посылает его диспетчеру.
3. Диспетчер посылает действие хранилищам по порядку. Каждое хранилище получает сообщение о всех действиях. Потом оно решает реагировать на действие или нет, и обновляет (или не обновляет) состояние соответственно.
4. После изменения состояния, хранилище оповещает контроллеры вида, которые подписаны на него.
5. Эти контроллеры вида просят у хранилища дать им обновленное состояние.
6. После того, как хранилище передаст свое состояние, контроллер вида попросит своих дочерние виды перерендериться в соответствии с новым состоянием.
Вот так я представляю себе работу Flux'а. Надеюсь, вам это тоже поможет!
Ресурсы