Flux для дурних людей

Намагаючись розібратися з бібліотекою від Facebook ReactJS і просуває тією ж компанією архітектурою «Flux», натрапив на просторах інтернету на дві цікаві статті: «ReactJS For Stupid People» та «Flux For Stupid People». Трохи раніше я поділився з хабравчанами перекладом першої статті, настала черга другої. Отже, поїхали.

Flux для дурних людей
TL;DR Мені, як дурному людині, як раз не вистачало цієї статті, коли я намагався розібратися з Flux. Це було не просто: хорошою документації немає й багато її частини переміщаються.

Це продовження статті «ReactJS For Stupid People».

чи Повинен я використовувати Flux?
Якщо ваш додаток працює з динамічними даними, тоді, ймовірно, ви повинні використовувати Flux.

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

Чому Flux?
Гумор в тому, що Flux — це не сама проста ідея. Так навіщо ж все ускладнювати?

90% iOS додатків — це дані в табличному вигляді. Інструменти для iOS мають чітко певні уявлення і модель даних, які спрощують розробку додатків.

Для frontend'a (HTML, JavaScript, CSS) у нас такого немає. Замість цього у нас є велика проблема: ніхто не знає, як структурувати frontend додаток. Я працював у цій сфері протягом багатьох років і «кращі практики» ніколи нас цього не вчили. Замість цього нас „вчили“ бібліотеки. jQuery? Angular? Backbone? Справжня проблема — потік даних — дотепер вислизає від нас.

Що таке Flux?
Flux — це термін, придуманий для позначення односпрямованого потоку даних з дуже специфічними подіями і слухачами. Немає Flux бібліотек(прим. перекл.: на даний момент їх повно), але вам буде потрібен Flux Dispatcher і будь-яка JavaScript event-бібліотека.

Офіційна документація написана як чийсь потік свідомості і є поганий відправною точкою. Але коли ви укладете Flux в свою голову, вона може допомогти заповнити деякі прогалини.

Не намагайтеся порівнювати Flux MVC-архітектурою. Проведення паралелей тільки ще більше заплутає вас.

Давайте пірнемо глибше! Я буду по порядку пояснювати всі концепції і зводити їх до одного.

1. Ваші уявлення відправляють події

Пошук по своїй суті є event-системою. Він траслирует події і реєструє колбэки. Є тільки один глобальний dispather. Ви можете використовуватиdispather від Facebook. Він дуже легко ініціалізується:

var AppDispatcher = new Dispatcher();

Скажімо, у вашому додатку є кнопка «New Item», яка додає новий елемент в список.

<button onClick={ this.createNewItem }>New Item</button> 

Що відбувається при натисканні? Ваше уявлення відправляє спеціальне подія, яка містить в собі назву події і дані нового елемента:

createNewItem: function( evt ) {

AppDispatcher.dispatch({
eventName: 'new-item',
newItem: { name: 'Marco' } // example data
});

}


2. Ваше сховище(store) реагує на надіслані події

Як і „Flux“, „Store“ — це просто термін, придуманий Facebook. Для нашого додатка нам необхідні деякий набір логіки і дані для списку. Це і є наше сховище. Назвемо його ListStore.

Сховище — це одинак, а це означає, що вам можна не оголошувати його через оператор new.

// Global object representing list data and logic
var ListStore = {

// Actual collection of model data
items: [],

// Accessor method we'll use later
getAll: function() {
return this.items;
}

}

Ваше сховище буде реагувати на надіслане подія:

var ListStore = ...

AppDispatcher.register( function( payload ) {

switch( payload.eventName ) {

case 'new-item':

// We get to mutate data!
ListStore.items.push( payload.newItem );
break;

}

return true; // Needed for Flux promise resolution

});


Це традиційний підхід до того, як Flux викликає колбэки. Об'єкт payload містить в собі назву події і дані. А оператор switch вирішує яку дію виконати.

Ключова концепція: Сховище — це не модель. Сховище містить моделі.

Ключова концепція: Сховище — єдина сутність у вашому додатку, яка знає як змінити дані.Це найважливіша частина Flux. Подія, які ми послали, не знає як додати або видалити елемент
.

Якщо, наприклад, різних частинах вашого додатка потрібно зберігати шлях до деяких картинок та інші метадані, ви створюєте інше сховище і називаєте його ImageStore. Сховище являє собою окремий „домен“ вашої програми. Якщо ваш додаток велике, домени, можливо, будуть для вас очевидні. Якщо додаток маленьке, то, можливо, вам вистачить і одного сховища.

Тільки сховища реєструють колбеки в dispatcher. Ваші уявлення ніколи не повинні викликати AppDispatcher.register. Dispatcher тільки для відправлення повідомлень з вистав у сховища. Ваші уявлення будуть реагувати на інший вид подій.

3. Ваше сховище посилає подія „Change“

Ми майже закінчили. Зараз наші дані точно змінюються, залишилося розповісти про це світу.

Ваше сховище посилає подія, але не використовує dispather. Це може збити з пантелику, але це „Flux way“. Давайте дамо нашому сховища можливість ініціювати подія. Якщо ви використовуєте MicroEvents.js, то це просто:

MicroEvent.mixin( ListStore ); 

Тепер ініціалізуємо наша подія „change“:

case 'new-item':

ListStore.items.push( payload.newItem );

// Tell the world we changed!
ListStore.trigger( 'change' );

break;

Ключова концепція: Ми не передаємо дані разом з подією. Наше уявлення турбуватися тільки про те, що щось змінилося.

4. Ваше уявлення реагує на подію „change“

Зараз ми повинні відобразити список. Наше уявлення повністю перерисуется. коли список зміниться. Це не помилка.

По-перше, давайте підпишемося на подію „change“ з нашого ListStore відразу після створення компонента:

componentDidMount: function() { 
ListStore.bind( 'change', this.listChanged );
}

Для простоти ми просто викличемо forceUpdate, який викличе перемальовування:

listChanged: function() { 
// Since the list changed, trigger a new render.
this.forceUpdate();
},

Не забуваємо видаляти слухача, коли компонент видаляється:

componentWillUnmount: function() { 
ListStore.unbind( 'change', this.listChanged );
},

Що тепер? Давайте подивимося на нашу функцію render, яку я навмисно залишив наостанок:

render: function() {

// Remember, ListStore is global!
// There's no need to pass it around
var items = ListStore.getAll();

// Build list items markup by looping
// over the entire list
var itemHtml = items.map( function( listItem ) {

// "key" is important, should be a unique
// identifier for each list item
return <li key={ listItem.id }>
{ listItem.name }
</li>;

});

return <div>
<ul>
{ itemHtml }
</ul>

<button onClick={ this.createNewItem }>New Item</button>
</div>;
}


Ми прийшли до повного циклу. Коли ви додаєте новий елемент, подання відправляє подія, сховище підписана на це подія, сховище змінюється, сховище створює подія „change“ і подання, підписане на подію „change“, перемальовується.

Але тут є одна проблема. Ми повністю перемальовуємо подання кожен раз, коли список змінюється! Хіба це не жахливо неефективно?

Звичайно, ми викликаємо функцію render знову і, звичайно, весь код в цій функції виконується. Але React змінює реальний DOM, якщо тільки результат виклику render буде відрізняться від попереднього. Ваша функція render, насправді, генерує „віртуальний DOM“, який React порівнює з попереднім результатом виклику функції render. Якщо два віртуальних DOMа розрізняються, React змінить реальний DOM — і тільки в потрібних місцях.

Ключова концепція: Коли сховище змінюється, ваші уявлення не повинні піклуватися про те, яка подія відбулася: додавання, видалення або редагування. Вони повинні просто повністю перерисоваться. Алгоритм порівняння „вирутального DOM“ впорається з важкими розрахунками і змінить реальний DOM. Це зробить ваше життя простіше і зменшить головний біль.

І ще: що таке взагалі „Action Creator“?
Пам'ятаєте, коли ми натискали нашу кнопку, ми відправляли спеціальна подія:

AppDispatcher.dispatch({ 
eventName: 'new-item',
newItem: { name: 'Samantha' }
});

Це може призвести до часто повторюваного коду, якщо багато ваших уявлень використовує цю подію. Плюс, всі подання повинні знати про форматі. Це неправильно. Flux пропонує абстракцію, названу action creators, яка просто абстрагує код вище в функцію.

ListActions = {

add: function( item ) {
AppDispatcher.dispatch({
eventName: 'new-item',
newItem: item
});
}

};

Тепер, ваше уявлення просто викликає ListAction.add({name: "..."}) і не переживає про синтаксис відправки повідомлень.

Залишилися питання

Все, про що говорить нам Flux, це як управляти потоком даних. Але він не відповідає на питання:
  • Як вам завантажувати дані на сервер і як їх зберігати на сервері?
  • Як керувати зв'язком між компонентами з загальним батьком?
  • Яку event-бібліотеку використовувати? Чи має це значення?
  • Чому Facebook не включив все це в свою бібліотеку?
  • чи Повинен я використовувати шар моделі зразок Backbone в якості моделі в нашому сховищі?


Відповідь на всі ці запитання: розважайтеся!

PS: Не використовуйте forceUpdate
Я використовував forseUpdate заради простоти. Правильне рішення буде вважати дані зі сховища і скопіювати їх в state компонента, а у функції render прочитати дані з state. Ви можете подивитися, як це працює в цьому приклад.

Коли ваш компонент завантажується, сховище копіює state. Коли сховище змінюється, дані повністю переписуються. І це краще, тому що всередині forceUpdate виконується синхронно, а setState — більш ефективний.

От і все!

На додаток можете подивитися Example Flux Applcation від Facebook.

Сподіваюся, після прочитання цієї статті вам буде простіше зрозуміти файлову структуру проекту.

Документація Flux містить кілька корисних прикладів, глибоко закопані всередині.

Якщо цей пост допоміг вам зрозуміти Flux, то підписуйтесь на мене в twitter.

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

0 коментарів

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