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


Узнаем Flux архитектурное решение React.js

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

Он говорит, что Facebook обеспечивает репозиторий, который включает в себя библиотеку Диспетчер (Dispatcher). Диспетчер сортирует глобальные pub/sub обработчики которые транслирую нагрузку зарегистрированным обработчикам (callbacks).

Типичная Flux архитектура будет рычагом библиотеки Диспетчера (Dispatcher library), наряду с модулем NodeJS’s под названием EventEmitter для того, чтобы установить систему событий, которая поможет управлять состоянием приложения.

Flux возможно лучше объясняется его индивидуальными компонентами:

Actions – Вспомогательные методы, которые облегчают передачу данных в Диспетчер (Dispatcher)
Dispatcher – Получает действия и транслирует нагрузку в зарегистрированные обработчики (callbacks)
Stores – Хранилища для логики и состояния приложения, у которых есть обработчики зарегистрированные в Диспетчере
Controller Views – React компоненты, которые захватывают состояние из Хранилищ и передают их вниз через свойства к дочерним компонентам.

Давайте посмотрим как этот процесс выглядит графически:
flux react

Когда вы работаете с данными, которые приходят извне (или отдаются наружу), использование Actions для внедрения данных в поток Flux и впоследствии в Stores, это наименее болезненный путь работы с подобной системой.

Диспетчер (Dispatcher)

Диспетчер (Dispatcher) по существу администратор целого процесса. Он - ядро вашего приложения. Dispatcher получает действия и пересылает действия и данные зарегистрированным обработчикам (callbacks).

Он что основной издатель/подписчик?
Не совсем. Dispatcher транслирует нагрузку всем зарегистрированным обработчикам (callbacks), и включает функциональность которая позволяет вам вызывать обработчики (callbacks) в определенном порядке, даже ожидать обновления перед продолжением. Существует только один диспетчер (dispatcher) и он действует как центральное ядро вашего приложения.


var Dispatcher = require('flux').Dispatcher;
var AppDispatcher = new Dispatcher();

AppDispatcher.handleViewAction = function(action) {
this.dispatch({
source: 'VIEW_ACTION',
action: action
});
}

module.exports = AppDispatcher;


В примере выше, мы создали экземпляр нашего Диспетчера (Dispatcher) и добавили метод. Эта абстракция поможет вам увидеть отличия между:
событиями сработавшими в браузере
vs
сервер/api сработавшими событиями.

Наш метод вызывает пересылающий метод, который будет транслировать нагрузку действий ко всем обработчикам(callbacks) которые к нему привязаны. Это действие может быть затем задействовано на Хранилищах (Stores) и в результате обновит state.

Диаграмма ниже иллюстрирует этот процесс:



Зависимости

Одна из лучших частей Диспетчера (Dispatcher) это способность определять зависимости и группировать обработчики в наших Хранилищах (Stores). Если одна часть нашего приложения зависит от другой, которая должна быть сначала обновлена, чтобы отобразиться (render) должным образом, то имеющийся у Диспетчера (Dispatcher) метод waitFor может быть очень полезным.

Для того, чтобы использовать эту особенность нам необходимо сохранить возвращенное значение регистрационного метода Диспетчера в нашем Хранилище (Store) как dispatcherIndex, как показано ниже:

ShoeStore.dispatcherIndex = AppDispatcher.register(function(payload) {

});


Затем в нашем Хранилище (Store), когда обрабатывается переданное действие, мы можем использовать waitFor метод Диспетчера, для гарантированного обновления нашего ShoeStore:

case 'BUY_SHOES':
AppDispatcher.waitFor([
ShoeStore.dispatcherIndex
], function() {
CheckoutStore.purchaseShoes(ShoeStore.getSelectedShoes());
});
break;


Хранилища (Stores)

В Flux, Хранилища (Stores) управляют состоянием приложения в частной области видимости внутри вашего приложения. Это означает, что в app части, хранилища управляют данными, данные возвращают методы и обработчики (callbacks) диспетчера.

Давайте посмотрим на стандартное Хранилище:

var AppDispatcher = require('../dispatcher/AppDispatcher');
var ShoeConstants = require('../constants/ShoeConstants');
var EventEmitter = require('events').EventEmitter;
var merge = require('react/lib/merge');

// внутренний объект shoes
var _shoes = {};

// Метод для загрузки shoes из action data
function loadShoes(data) {
_shoes = data.shoes;
}

// Соединяем наше хранилище (store) с Node Event Emitter
var ShoeStore = merge(EventEmitter.prototype, {

// Возвращаем все shoes
getShoes: function() {
return _shoes;
},

emitChange: function() {
this.emit('change');
},

addChangeListener: function(callback) {
this.on('change', callback);
},

removeChangeListener: function(callback) {
this.removeListener('change', callback);
}

});

// Регистрируем обработчик (dispatcher callback) диспетчера
AppDispatcher.register(function(payload) {
var action = payload.action;
var text;
// Определяем действия для определенных дейстивя (actions)
switch(action.actionType) {
case ShoeConstants.LOAD_SHOES:
// Вызываем внутренний метод основанный на переданном действии
loadShoes(action.data);
break;

default:
return true;
}

// Если действие было выполнено, emit меняет событие (If action was acted upon, emit change event)
ShoeStore.emitChange();

return true;

});

module.exports = ShoeStore;


Самая важная вещь, что мы сделали выше, это расширили наш store с помощью NodeJS EventEmitter. Это позволяет нашим хранилищам (stores) слушать/транслировать события. Это позволяет нашим Представлениям/Компонентам обновляться основываясь на этих событиях. Из-за того, что наш Контроллер Представления (Controller View) слушает наши хранилища (Stores), leveraging this to emit change events позволит Контроллеру Представления знать, что состояние нашего приложения изменено и настало время извлекать состояние, чтобы поддерживать его актуальным.

Мы также зарегистрировали "обратный вызов" с помощью нашего AppDispatcher использующего его метод register. Это означает что наше Хранилище теперь слушает вещание AppDispatcher. Наш switch определяет будут ли искаться любые действия подходящие для данного вещания. Если подходящее действие найдено, событие изменения будет излучено и представления которые слушают это событие обновят свои состояния.

Controller View

Наш публичный метод getShoes используется нашем Контроллером представления для нахождения всех ботинок (shoes) в нашем объекте _shoes и использует эти данные в нашем состоянии компонента (components state). Пока это простой пример, сложная логика может быть вставлена сюда взамен наших представлений.

Создатели Действий и Действия

Создатели Действий это коллекции методов, которые вызываются представлениями (или чем-нибудь еще в таком роде) посылающими действия Диспетчеру. Действия фактически транслируют то, что они передают через диспетчер.

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

Определение констант:

var keyMirror = require('react/lib/keyMirror');

module.exports = keyMirror({
LOAD_SHOES: null
});


Выше мы использовали библиотеку Реакта keyMirror, она отзеркаливает наши ключи так, что наши значения совпадают с определенными нами ключами. Только посмотрите на этот файл, мы можем сказать нашему app загрузить туфли (shoes). Использование констант поможет держать данные организованными и даст высокий уровень представлению что же в итоге делает наше приложение в действительности.

В нашем примере выше мы создали метод нашего объекта ShoeStoreActions, который вызывает наш диспетчер вместе с данными которыми мы его обеспечили. Теперь мы можем импортировать этот файл действий в наше Представление или API и вызвать ShoeStoreActions.loadShoes(ourData) чтобы послать наше вещание в Диспетчер, который будет транслировать его. Затем ХранилищеБотинок (ShoeStore) будет слышать, эти события и вызывать метод, который загрузит некоторое количество ботинок.


Контроллер Представлений (Controller Views)

Контроллер Представлений это компонент Реакта, который слушает события изменений и извлекает состояние Приложения из Хранилищ. Затем они передают вниз данные их дочерним компонентам через свойства (props).

Controller View


Вот как это выглядит:
/** @jsx React.DOM */

var React = require('react');
var ShoesStore = require('../stores/ShoeStore');

// Метод доставляет состояние приложения из хранилища
function getAppState() {
return {
shoes: ShoeStore.getShoes()
};
}

// Создать наш компонент class
var ShoeStoreApp = React.createClass({

// Использовать метод getAppState чтобы инициализировать состояние
getInitialState: function() {
return getAppState();
},

// Слушаем изменения
componentDidMount: function() {
ShoeStore.addChangeListener(this._onChange);
},

// Уничтожаем слушатель изменений
componentWillUnmount: function() {
ShoesStore.removeChangeListener(this._onChange);
},

render: function() {
return (

);
},

// Обновляем состояние представления, когда событие изменения получено
_onChange: function() {
this.setState(getAppState());
}

});

module.exports = ShoeStoreApp;


В примере выше мы слушаем события изменений используя addChangeListener и обновляем состояние нашего приложения когда событие получено.

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

Сложим все вместе

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

Flux