Статьи / JavaScript / Reactjs (документация, руководство, примеры, flux) / Flux


Обзор Flux

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

Flux приложения имеют три основные части: диспетчер, хранилища, и отображения (вид, компоненты React). Их не следует путать с Model-View-Controller. Контроллеры существуют в (Flux)потоке приложения, но они контроллер-вид - вид часто встречается в верхней части иерархии, где извлекает данные из хранилищь и передавает эти данные вплоть до их детей. Кроме того, создатели дейстия - вспомогательные методы диспетчера - используются для поддержки семантики API, которое описывает все изменения, которые возможны в приложении. О них полезно думать, как о четвертой части Flux(потока) цикла обновления.

Flux избегает MVC в пользу однонаправленного потока данных. Когда пользователь взаимодействует с React представлением(видом), вид распространяет действие через центрального диспетчера, в различные хранилища, которые содержат данные приложения и бизнес-логику, которая обновляет все виды, которые затронуты. Это особенно хорошо работает с декларативным стилем программирования React, который позволяет хранилищу посылать обновления без указания, как перемещать представления(виды) между состояниями.

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

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

Структура и поток данных


Данные в потоке приложения текут в одном направлении:
однонаправленный поток данных в движении
Однонаправленный поток данных является основным в паттерне Flux и диаграмма выше должна быть основной ментальной моделью для Flux программиста. Диспетчер, хранилища и виды независимых узлов с различными входами и выходами. Действия простых объектов, содержащие новые данные и идентификационный тип свойства.

Представления (виды) могут вызвать новое действие распространяющееся через систему в ответ на действия пользователя:
Поток данных в Flux с данными поступающими от взаимодействия с пользователем

Все данные текут через диспетчер в качестве центрального концентратора(хаба). Действия предоставляются диспетчеру в метод "action creator", и чаще всего происходят из взаимодействия пользователя с видом. Затем диспетчер вызывает функции обратного вызова, которые были зарегистрированы в хранилищах, распределяя действия на все хранилища. В рамках своих обратных вызовов, хранилища реагируют на какие-то действия, связанные с состоянием, которое они поддерживают. Затем хранилища испускают событие изменения предупредающее контроллер-видов, что измение в слое данных произошло. Контроллер-видов слушает эти события и извлекает данные из хранилищ, в обработчике событий. Контроллер-видов вызывает их собственный setState() метод, создающий повторный рендеринг себя и всех своих потомков в дереве компонентов.
Изменяющиеся передачи между каждым из шагов потока данных Flux

Такая структура позволяет нам легко рассуждать о нашем приложении, как о функциональном реактивном программировании, или, более конкретно программировании потока данных или на основе потока программирования, где данные протекают через приложения в одном направлении - нет двух способов привязки. Состояние приложения поддерживается только в хранилищах, позволяя различным частям приложения по-прежнему быть жестко разделенными. Где зависимости возникают между хранилищами, они хранятся в строгой иерархии, с синхронным обновлением, которым управляет диспетчер.

Мы обнаружили, что двойные привязки данных приводят к каскадным обновлениям, когда изменение одного объекта приводит к очередному изменению объекта, который также может вызвать ещё больше обновлений. По мере роста приложения, эти каскадные обновления, очень трудно предсказать, что измениться в результате одного действия пользователя. Когда обновления могут изменить данные только в одном раунде, система в целом становится более предсказуемой.

Давайте посмотрим на различные части потока поближе. Давайте начнем с диспетчера.

Один Диспетчер


Диспетчер центральный распределитель, который управляет всем потоком данных во Flux приложении. По существу, это реестр обратных вызовов в хранилищах и не имеет никакого своего реального интеллекта - это простой механизм для распространения действия на хранилища. Каждое хранилище регистрирует себя и обеспечивает обратный вызов. Когда action creator обеспечивает диспетчер новым действием, все хранилища в приложении получают действие с помощью обратного вызова в реестре.

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

Тот же диспетчер, что Facebook использует в продакшн, доступен через npm, Bower , или GitHub.

Хранилища


Хранилища содержат состояние и логику приложения. Их роль немного похожа на модель в традиционном MVC, но они управляют состоянием многих объектов - они не представляют собой запись данных как делают модели ORM. Они не такие же, как коллекции Backbone. Больше, чем просто управление коллекциями объектов в ORM-стиле, хранилища управляют состоянием приложения для конкретной области внутри приложения.

Например, Facebook, Lookback Video Editor использовал TimeStore, чтобы следить за временем воспроизведения и состоянием воспроизведения. С другой стороны, ImageStore из того же приложения следит за коллекциями изображений. TodoStore в нашем TodoMVC примере похоже управляет коллекцией задач. Хранилище имеет характеристики как коллекции моделей, так и синглтона модели логической области.

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

Виды и контроллер-видов


React обеспечивает ряд компонуемых и свободно повторно рендеримых видов, что является необходимым для слоя представления(вида). Рядом с вершиной вложенной иерархии представлений, особый род вида прослушивает события, которые транслируются по хранилищам, от которых они зависят. Мы называем это контроллер-вид, так как он обеспечивает скрепляющий код для получения данных из хранилищ, и передачи этих данные по цепочке своих потомков. Мы должны бы иметь один из этих контроллер-видов управляющих любой важной частью страницы.

Когда он получает событие из хранилища, он сначала запрашивает новые данные, которые ему нужны с помощью публичных getter методов хранилища. Затем он вызывает свои собственные setState() или forceUpdate() методы, в результате чего его render() метод и render() метод всех его потомков запускаются.

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

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

Действия


Диспетчер предоставляет метод, который позволяет нам вызвать отправку в хранилища, и включает в себя полезную нагрузку данных, которые мы называем действие. Создатель действия может быть обернут в семантический вспомогательный метод, который посылает действие диспетчеру. Например, мы хотим изменить текст пункта в списке приложения Дел. Мы создаем действие с сигнатурой функции, как updateText(todoId, newText) в нашем TodoActions модуле. Этот метод может быть вызван из обработчиков событий нашего вида, поэтому мы можем вызвать его в ответ на действия пользователя. Этот метод создателя действия также добавляет type к действию, так что, когда действие интерпретируется в хранилище, он может реагировать соответствующим образом. В нашем примере, этот тип может быть назван чем-то вроде TODO_UPDATE_TEXT.

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

Как насчет диспетчера?



Как упоминалось ранее, диспетчер также может управлять зависимостями между хранилищами. Эта функциональность доступна через waitFor() метод в классе Dispatcher. Мы не должны использовать этот метод в чрезвычайно простом приложении TodoMVC, но это становится жизненно важным в более крупных и сложных приложениях.

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

case 'TODO_CREATE': 
Dispatcher.waitFor([
PrependedTextStore.dispatchToken,
YetAnotherStore.dispatchToken
]);

TodoStore.create(PrependedTextStore.getText() + ' ' + action.text);
break;


waitFor() принимает единственный аргумент, который является массивом диспетчерских индексов реестра, которые часто называют диспетчерские маркеры. Таким образом, хранилище которое вызывает waitFor() может зависеть от состояния другого хранилища, чтобы сообщить, как оно должно обновить свое состояние.

register() возвращает диспетчерский маркер при регистрации обратных вызовов для диспетчера (Dispatcher):
PrependedTextStore.dispatchToken = Dispatcher.register(
function (payload) {
// ...
});


Более подробную информацию о waitFor(), действиях, создателях действий и диспетчере, пожалуйста, см Flux: действия и диспетчер.