Розбираємося з Flux, реактивної архітектурою від facebook



Введення
Ласкаво просимо в третю частину серії статей «Вивчаємо React». Сьогодні ми будемо вивчати, як влаштована архітектура Facebook Flux, і як використовувати її в своїх проектах.

Насамперед, я раджу ознайомитися з першими двома статтями цієї серії, Getting Started & Concepts і Building a Real Time Twitter Stream with Node and React. Їх прочитання не є обов'язковим, однак напевно може допомогти вам зрозуміти цю статтю, якщо ви ще недостатньо знайомі з React.js

Що таке Flux?
Flux — це архітектура, яку команда Facebook використовує при роботі з React. Це не фреймворк, або бібліотека, це новий архітектурний підхід, який доповнює React і принцип односпрямованого потоку даних.

Тим не менш, Facebook надає репозиторій, який містить реалізацію Dispatcher. Диспетчер грає роль глобального посередника в шаблоні «Видавець-читач» (Pub/sub) і розсилає корисну навантаження зареєстрованих обробників.

Типова реалізація архітектури Flux може використовувати цю бібліотеку разом з класом EventEmitter з NodeJS, щоб побудувати подієво-орієнтовану систему, яка допоможе керувати станом додатки.

Ймовірно, Flux легше всього пояснити, виходячи з складових його компонентів:
  • Actions / Дії — хелпери, які полегшують передачу даних Диспетчера
  • Dispatcher / Диспетчер — приймає Дії і розсилає навантаження зареєстрованим обробників
  • Stores / Сховища — контейнери для стану програми та бізнес-логіки в обробниках, зареєстрованих в Диспетчері
  • Controller Views / Подання — React-компоненти, які збирають стан сховищ і передають його дочірнім компонентів через властивості


Давайте подивимося, як цей процес виглядає у вигляді діаграми:


Як до цього ставиться API?

На мій погляд, використання Actions для передачі даних Сховищ через потік Flux — найменш болісний спосіб роботи з даними, що приходять ззовні вашої програми, чи відправляються назовні.

Dispatcher / Диспетчер
Що ж таке Диспетчер?

В сутності, Менеджер — це менеджер всього цього процесу. Це центральний вузол вашої програми. Диспетчер отримує на вхід дії та розсилає ці дії (і пов'язані з ними дані) зареєстрованих обробників.

Так це насправді pub/sub?

Не зовсім. Диспетчер розсилає дані ВСІМ зареєстрованим в ньому обробників і дозволяє викликати обробники в певному порядку, навіть чекати оновлень перед тим, як продовжити роботу. Є тільки один Диспетчер, і він діє як центральний вузол всього вашого додатка.

Ось як це може виглядати:

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

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

module.exports = AppDispatcher;

У наведеному вище прикладі ми створюємо екземпляр Диспетчера і метод handleViewAction. Ця абстракція корисна, якщо ви збираєтеся розділяти дії, створені в інтерфейсі та дії, які прийшли від сервера / API.

Наш метод викликає метод dispatch, який вже розсилає дані action всім зареєстрованим в ньому обробників. Ця дія може бути оброблено Сховищами, в результаті чого стан програми буде оновлено.

Наступна діаграма ілюструє цей процес:


Залежно
Однією з приємних деталей описаної реалізації Диспетчера є можливість описати залежності і керувати порядком виконання обробників в Сховищах. Отже, якщо для коректного відображення стану один з компонентів програми залежить від іншого, який повинен оновитися перед ним, стане в нагоді метод Диспетчера waitFor.

Щоб використовувати цю можливість, необхідно зберегти значення, що повертається з методу реєстрації в Диспетчері, властивості dispatcherIndex Сховища, як показано далі:

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

});

Потім в Сховище, при обробці Дії, ми можемо використовувати метод waitFor Диспетчера, щоб переконатися, що до цього моменту ShoeStore вже встиг обробити Дію і оновити дані:

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

Прим. пер.: Ken Wheeler, очевидно, описує застарілу реалізацію Диспетчера, т. к. в актуальній версії метод waitFor має іншу сигнатуру.

Stores / Сховища
Сховища в Flux керують станом певних частин предметної області вашого додатка. На більш високому рівні це означає, що Сховища зберігають дані, методи отримання цих даних та зареєстровані в Диспетчері обробники Дій.

Давайте поглянемо на просте Сховище:

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 з даних Дії
function loadShoes(data) {
_shoes = data.shoes;
}

// Додати можливості Event Emitter з Node
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);
}

});

// Зареєструвати обробник у Диспетчері
AppDispatcher.register(function(payload) {
var action = payload.action;
var text;
// Обробити Дію в залежності від його типу
switch(action.actionType) {
case ShoeConstants.LOAD_SHOES:
// Викликати внутрішній метод на підставі отриманого Дії
loadShoes(action.data);
break;

default:
return true;
}

// Якщо Дію було оброблено, створити подію "change"
ShoeStore.emitChange();

return true;

});

module.exports = ShoeStore;

Найважливіше, що ми зробили в прикладі вище — додали до нашого сховища можливості EventEmitter з NodeJS. Це дозволяє сховищ слухати і розсилати події, що, в свою чергу, дозволяє компонентам подання оновлюватися, відштовхуючись від цих подій. Так як наше уявлення слухає подія «change», створюване Сховищами, вона дізнається про те, що стан додатки змінилося, і пора отримати (і відображення) актуальний стан.

Також ми зареєстрували обробник у нашому AppDispatcher з допомогою його методу register. Це означає, що тепер наше Сховище тепер слухає оповіщення від AppDispatcher. Виходячи з отриманих даних, оператор switch вирішує, чи ми можемо обробити Дію. Якщо дію було оброблено, створюється подія «change», і Представлення, які підписалися на цю подію, реагують на нього оновленням свого стану:


Подання використовує метод getShoes інтерфейсу Сховища для того, щоб отримати всі shoes з внутрішнього об'єкта _shoes і передати ці дані компоненти. Це дуже простий приклад, проте така архітектура дозволяє компонентам залишатися досить акуратними, навіть якщо замість Уявлень використовувати більш складну логіку.

Action Creators & Actions / Фабрика Дій і Дії
Фабрика Дій — це набір методів, які викликаються з Уявлень (або з будь-яких інших місць) щоб відправити Дії Диспетчера. Дії і є тією корисним навантаженням, яку Диспетчер розсилає передплатникам.

У реалізації Facebook Дії розрізняються за типом — константі, яка надсилається разом з даними дії. В залежності від типу, Дії можуть бути відповідним чином оброблені зареєстрованих обробниках, при цьому дані з цих Дій використовуються як аргументи внутрішніх методів.

Ось як виглядають оголошення констант:

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

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

Вище ми використовували бібліотеку keyMirror з React щоб, як ви здогадалися, створити об'єкт зі значеннями, ідентичними своїм ключем. Просто подивившись на цей файл, можна сказати, що наш додаток вміє завантажувати shoes. Використання констант дозволяє все впорядкувати і допомагає швидко оцінити можливості програми.

Давайте тепер подивимося на оголошення відповідною Фабрики Дій:

var AppDispatcher = require('../dispatcher/AppDispatcher');
var ShoeStoreConstants = require('../constants/ShoeStoreConstants');

var ShoeStoreActions = {

loadShoes: function(data) {
AppDispatcher.handleAction({
actionType: ShoeStoreConstants.LOAD_SHOES,
data: data
})
}

};

module.exports = ShoeStoreActions;

У наведеному прикладі ми створили в нашому об'єкті ShoeStoreActions метод, який передає нашому Диспетчеру зазначені дані. Тепер ми можемо завантажити цей файл з нашого API (або, наприклад, Уявлень) і викликати метод ShoeStoreActions.loadShoes(ourData), щоб передати корисну навантаження Диспетчеру, який розішле її передплатникам. Таким чином ShoeStore дізнається про цю подію і викличе метод завантаження яких-небудь shoes.

Controller Views / Подання
Уявлення — це всього лише React-компоненти, які підписані на подію «chnge» і отримують стан програми зі Сховищ. Далі вони передають ці дані дочірнім компонентів через властивості.


Ось як це виглядає:

/** @jsx React.DOM */

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

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

// Створюємо React-компонент
var ShoeStoreApp = React.createClass({

// Використовуємо метод getAppState, щоб встановити початковий стан
getInitialState: function() {
return getAppState();
},

// Підписуємося на оновлення
componentDidMount: function() {
ShoeStore.addChangeListener(this._onChange);
},

// Повідписуємося від оновлень
componentWillUnmount: function() {
ShoesStore.removeChangeListener(this._onChange);
},

render: function() {
return (
<ShoeStore shoes={this.state.shoes} />
);
},

// Оновлюємо стан Подання у відповідь на подію "change"
_onChange: function() {
this.setState(getAppState());
}

});

module.exports = ShoeStoreApp;

Прим. пер.: В актуальній версії React компоненти створюються дещо по-іншому.

У наведеному вище прикладі ми підписуємося на оновлення Сховища, використовуючи addChangeListener, і оновлюємо наш стан, коли отримаємо подія «change».

Стан додатки зберігається в наших Сховищах, тому ми використовуємо інтерфейс Сховищ, щоб отримати ці дані, а потім оновити стан компонентів.

Збираємо всі разом
Тепер, коли ми пройшлися по всім основним частинам архітектури Flux, ми краще розуміємо, як ця архітектура працює насправді. Пам'ятаєте нашу діаграму процесів з початку статті? Давайте поглянемо на них трохи докладніше, так як ми тепер розуміємо функції кожної частини потоку:



Висновок
Сподіваюся, що ця стаття допомогла вам краще зрозуміти архітектуру Flux від Facebook. Я навіть не підозрював, наскільки зручний React.js, поки не спробував його в дії.

Використавши одного разу Flux, ви відчуєте, що написання додатків на React без Flux схоже на маніпуляції з DOM без jQuery. Так, це можливо, але виглядає менш витончено і впорядковано.

Якщо ви хочете дотримуватися архітектури Flux, але вам не подобається React, спробуйте Delorean, Flux-фреймворк, який можна поєднати з Ractive.js або Flight. Ще одна варта уваги бібліотека — Fluxxor, яка використовує дещо інший підхід до архітектури Flux і передбачає більш жорстку зв'язок компонентів Flux у складі єдиного екземпляра.

Я вважаю, що для того, щоб повністю зрозуміти Flux, його необхідно випробувати в справі, тому залишайтеся з нами, щоб прочитати четверту, заключну частину циклу статей з вивчення React, де ми створимо простий онлайн-магазин, використовуючи React.js і архітектуру Flux.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.