Flux и Redux
Flux представляет архитектуру приложений, которые используют React. Flux больше представляет паттерн, чем конкретный фреймворк.
Приложения, использующие Flux, имеют три основные части: диспетчер (dispatcher) , хранилище данных (store) и представления (view) — стандартные компоненты React.
Диспетчер представляет во всей этой схеме центральное звено, которое управляет потоком данных в приложении Flux. Диспетчер регистрирует хранилища и их коллбеки — обратные вызовы. Когда диспетчер получает извне некоторое действие, то через коллбеки хранилищ диспетчер уведомляет эти хранилища о поступившем действии.
Хранилища содержат состояние приложения и его логику. По своему действию они могут напоминать модель из паттерна MVC, в то же время они не представляют один объект, а управляют группой объектов. Каждое отдельное хранилище управляет определенной областью или доменом приложения.
Как было описано выше, каждое хранилище регистрируется в диспетчере вместе со своими обратными вызовами. Когда диспетчер получает действие, то он выполняет обратный вызов, передавая ему поступившее действие в качестве параметра. В зависимости от типа действия вызывается тот или иной метод внутри хранилища, в котором происходит обновление состояния хранилища. После обновления хранилища генерируется событие, которое указывает, что хранилище было обновлено. И через это событие представления (то есть компоненты React) узнают, что хранилище было обновлено, и сами обновляют свое состояние.
Представления оформляют визуальную часть приложения. Особый вид представлений — controller-view представляет компонент самого верхнего уровня, который содержит все остальные компоненты. Controller-view прослушивает события, которые исходят от хранилища. Получив событие, controller-view передает данные, полученные от хранилища, другим компонентам.
Когда controller-view получает событие от хранилища, то вначале controller-view запрашивает у хранилища все необходимые данные. Затем он вызывает свой метод setState() или forceUpdate() , который приводит к выполнению у компонента метода render() . А это в свою очередь приводит к вызову метода render() и обновлению дочерних компонентов.
Нередко состояние хранилища передается по иерархии компонентов в виде единого объекта, а компоненты извлекают из него только те данные, которые им непосредственно нужны.
Действие представляет функцию, которая может содержать некоторые данные, которые передаются диспетчеру. Действие может быть вызвано обработчиками событий в компонентах, например, по нажатию на кнопку, либо инициатором действий может какой-нибудь другой внешний источник, например, сервер. Через диспетчер хранилище получает действие и соответствующим образом реагирует на него.
Весь механизм взаимодействия можно представить в виде однонаправленного потока от действия (action) к представлению (view):
Когда пользователь взаимодействует с представлением, то представления через диспетчер вызывают различные действия (например, добавление или обновление данных) по отношению к хранилищу, обратно от хранилища к представлению идут ответные действия, которые обновляют представление.
Рассмотрим действие архитектуры Flux на примере. Но стоит отметит, что на момент написания данной статьи (март 2022 года) самая последняя версия flux пока не поддерживала react 18, поэтому далее в проекте будет использоваться react 17.
Итак, создадим новый проект и для этого определим новый каталог fluxapp . Вначале добавим в него новый файл package.json :
< "name": "fluxapp", "description": "A React.js project using Flux", "version": "1.0.0", "author": "metanit.com", "scripts": < "dev": "webpack serve", "build": "webpack" >, "dependencies": < "react": "17.0.0", "react-dom": "17.0.0", "flux": "4.0.0", "immutable": "4.0.0" >, "devDependencies": < "@babel/cli": "7.17.0", "@babel/core": "7.17.0", "@babel/preset-react": "7.16.0", "@babel/preset-env": "7.16.0", "babel-loader": "8.2.0", "webpack": "5.70.0", "webpack-cli": "4.10.0", "webpack-dev-server": "4.7.0" >>
Кроме зависимостей react и react-dom здесь добавлена зависимость flux. Кроме того, так как приложение будет разбито на отдельные части, то для их компиляции и сборки применяются пакеты babel и webpack.
Затем перейдем в командной строке/терминале к каталогу проекта и для установки пакетов выполним команду
npm install
Далее определим в проекте главную страницу index.html :
Flux в React
То есть все файлы приложения будут компилироваться в файл public/bundle.js , который подключается на веб-странице.
Для всей логики с использованием React создадим в проекте новую папку app .
Определение действий и диспетчера
И вначале определим, какие действия будет выполнять приложение. В нашем случае будет простейшее приложение, которое будет управлять списком объектов — добавлением и удалением. Поэтому добавим в папку app новый каталог data , в котором определим новый файл ActionTypes.js :
const ActionTypes = < ADD_ITEM: "ADD_ITEM", REMOVE_ITEM: "REMOVE_ITEM">; export default ActionTypes;
Итак, здесь определено два типа действий. И также в каталог app/data добавим новый файл PhonesDispatcher.js , который будет содержать определение диспетчера:
import from "flux"; export default new Dispatcher();
Диспетчер представляет объект класса Dispatcher из пакета flux.
И также добавим в каталог app/data новый файл Actions.js :
import ActionTypes from "./ActionTypes.js"; import PhonesDispatcher from "./PhonesDispatcher.js"; const Actions = < addItem(text) < PhonesDispatcher.dispatch(< type: ActionTypes.ADD_ITEM, text, >); >, removeItem(text) < PhonesDispatcher.dispatch(< type: ActionTypes.REMOVE_ITEM, text, >); > >; export default Actions;
Этот файл собственно определяет действия. Каждое действие определяется в виде функции, в которую могут передаваться параметры. В нашем случае список объектов будет представлять набор строк, поэтому в действия добавления и удаления элемента передается строка — добавляемый или удаляемый объект.
В самом действии вызывается метод dispatch . В качестве параметра этот метод принимает объект, в котором передаем тип действия и собственно данные. Но вообще в объекте можно определить любые данные, которые нам необходимы. При вызове действия этот объект будет передаваться в хранилище.
Определение хранилища
Далее также в каталоге app/data определим для хранилища файл PhoneStore.js :
import Immutable from "immutable"; import from "flux/utils"; import ActionTypes from "./ActionTypes.js"; import PhonesDispatcher from "./PhonesDispatcher.js"; class PhonesStore extends ReduceStore < constructor() < super(PhonesDispatcher); >getInitialState() < return Immutable.List.of("Apple iPhone 12 Pro", "Google Pixel 5"); >reduce(state, action) < switch (action.type) < case ActionTypes.ADD_ITEM: if (action.text) < return state.push(action.text); >return state; case ActionTypes.REMOVE_ITEM: let index = state.indexOf(action.text); if (index > -1) < return state.delete(index); >return state; default: return state; > > > export default new PhonesStore();
Хранилище представляет собой класс, унаследованный от класса ReduceStore из пакета «flux/utils». В конструкторе хранилища в конструктор базового класса передается объект диспетчера.
С помощью метода getInitialState() устанавливается состояние хранилища. В данном случае это список — объект Immutable.List . Он во многом аналогичен массиву javascript за тем исключением, что он является неизменяемым списком, а все операции с ним возвращают новый обновленный список. Подробнее про работу с такими коллекция можно посмотреть на странице immutable-js
В унаследованном методе reduce() получаем два объекта — state (текущее состояние хранилища, то, что изначально возвращается методом getInitialState) и action (тот объект, который передается в действии — то есть тип действия, добавляемый или удаляемый элемент). С помощью конструкции switch смотрим, какое действие было вызвано, и в зависимости от типа действия выполняем или добавление или удаление элемента.
Определение представления
Это была вся логика по работе с данными. Теперь определим визуальную часть. Для этого в каталог app добавим новую папку views . Далее в этой папке app/views создадим новый файл AppView.js :
import React from "react"; class AppView extends React.Component< constructor(props)< super(props); this.state = ; this.onInputChange = this.onInputChange.bind(this); this.onClick = this.onClick.bind(this); > onInputChange(e)< this.setState(); > onClick(e)< if(this.state.newItem)< this.props.onAddItem(this.state.newItem); this.setState(); > > render() < let remove = this.props.onRemoveItem; returnonChange= />; > > class Phone extends React.Component< constructor(props)< super(props); this.state = ; this.onClick = this.onClick.bind(this); > onClick(e) < this.props.onRemove(this.state.text); >render() < returnСписок смартфонов
< this.props.phones.map(function(item)< return text= onRemove= /> >) >; > > export default AppView;
Класс AppView представляет компонент верхнего уровня, в котором выводится список. Каждый элемент списка представлен отдельным компонентом Phone. Отдельные компоненты также можно было бы поместить в отдельные файлы, но для простоты я разместил их в одном файле.
Определение контейнера
Для соединения хранилищ, действий и представлений во Flux применяются контейнеры. Поэтому добавим в каталог app новую папку containers , в которой создадим файл AppContainer.js :
import AppView from "../views/AppView.js"; import from "flux/utils"; import React from "react"; import PhoneStore from "../data/PhoneStore.js"; import Actions from "../data/Actions.js"; class AppContainer extends React.Component < static getStores() < return [PhoneStore]; >static calculateState() < return < phones: PhoneStore.getState(), onAddItem: Actions.addItem, onRemoveItem: Actions.removeItem >; > render() < return onRemoveItem= onAddItem= />; > > export default Container.create(AppContainer);
Класс контейнера AppContainer, с одной стороны, представляет компонент React. Но в то же время он реализует два необходимых метода: getStores() и calculateState() .
Метод getStores() возвращает набор харнилищ, с которые используются в приложении. В нашем случае это только одно хранилище PhoneStore.
Метод calculateState() возвращает состояние контейнера. Здесь состояние контейнера включает список phones, причем этот список мы будем получать из состояния хранилища:
phones: PhoneStore.getState()
То есть, phones будет содержать объект Immutable.List.
Также в состоянии определяются два действия:
onAddItem: Actions.addItem, onRemoveItem: Actions.removeItem
Эти действия вместе со списком phones передаются в AppView, который создается в методе render. То есть таким образом представление AppView со всеми дочерними компонентами будет связано с хранилищем и действиями и с помощью обработчиков нажатия кнопок сможем вызывать действия.
В конце файла вызывается метод Container.create(AppContainer) , который создает сам контейнер.
Определение основного кода приложения
И в конце определим в папке app файл app.js , в котором будет происходить загрузка контейнера:
import AppContainer from "./containers/AppContainer.js"; import React from "react"; import ReactDOM from "react-dom"; ReactDOM.render(, document.getElementById("app"));
Затем в корневой папке проекта определим файл webpack.config.js , который будет соединять все файлы в одну сборку:
const path = require("path"); module.exports = < mode: "development", entry: "./app/app.js", // входная точка - исходный файл output:< path: path.resolve(__dirname, "./public"), // путь к каталогу выходных файлов - папка public publicPath: "/public/", filename: "bundle.js" // название создаваемого файла >, devServer: < historyApiFallback: true, static: < directory: path.join(__dirname, "/"), >, port: 8081, open: true >, module: < rules:[ //загрузчик для jsx < test: /\.jsx?$/, // определяем тип файлов exclude: /(node_modules)/, // исключаем из обработки папку node_modules loader: "babel-loader", // определяем загрузчик options:< presets:[ "@babel/preset-env", "@babel/preset-react"] // используемые плагины >> ] > >
В итоге весь проект будет выглядеть следующим образом:
Для компиляции и упаковки файлов перейдем в терминале/командной строке к папке проекта и вызовем команду:
npm run build
Эта команда сгенерирует файл public/bundle.js , который будет подключаться на веб-страницу.
И в конце запустим проект на выполнение командой npm run dev :
Flux для глупых людей
Пытаясь разобраться с библиотекой от Facebook ReactJS и продвигаемой той же компанией архитектурой «Flux», наткнулся на просторах интернета на две занимательные статьи: «ReactJS For Stupid People» и «Flux For Stupid People». Чуть раньше я поделился с хабравчанами переводом первой статьи, настала очередь второй. Итак, поехали.
Flux для глупых людей
TL;DR Мне, как глупому человеку, как раз не хватало этой статьи, когда я пытался разобраться с Flux. Это было не просто: хорошей документации нет и многие ее части перемещаются.
Должен ли я использовать 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. Ваши представления отправляют события
Dispatcher по своей сути является event-системой. Он траслирует события и регистрирует колбэки. Есть только один глобальный dispatcher. Вы можете использоватьdispatcher от Facebook. Он очень легко инициализируется:
var AppDispatcher = new Dispatcher();
Скажем, в вашем приложении есть кнопка “New Item”, которая добавляет новый элемент в список.
Что происходит при клике? Ваше представление отправляет специальное событие, которое содержит в себе название события и данные нового элемента:
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“
Мы почти закончили. Сейчас наши данные точно меняются, осталось рассказать об этом миру.
Ваше хранилище посылает событие, но не использует dispatcher. Это может сбить с толку, но это „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()
Для простоты мы просто вызовем forceUpdate, который вызовет перерисовку:
listChanged: function() < // Since the list changed, trigger a new render. this.forceUpdate(); >,
Не забываем удалять слушателя, когда компонент удаляется:
componentWillUnmount: function() < ListStore.unbind( 'change', this.listChanged ); >,
Что теперь? Давайте посмотрим на нашу функцию render, которую я намерено оставил напоследок:
Мы пришли к полному циклу. Когда вы добавляете новый элемент, представление отправляет событие, хранилище подписано на это событие, хранилище изменяется, хранилище создает событие „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() и не переживает о синтаксисе отправки сообщений.
Оставшиеся вопросы
- Как вам загружать данные на сервер и как их сохранять на сервере?
- Как управлять связью между компонентами с общим родителем?
- Какую event-библиотеку использовать? Имеет ли это значение?
- Почему Facebook не включил все это в свою библиотеку?
- Должен ли я использовать слой модели наподобие Backbone в качестве модели в нашем хранилище?
Ответ на все эти вопросы: развлекайтесь!
PS: Не используйте forceUpdate
Я использовал forseUpdate ради простоты. Правильное решение будет считать данные из хранилища и скопировать их в state компонента, а в функции render прочитать данные из state. Вы можете посмотреть, как это работает в этом примере.
Когда ваш компонент загружается, хранилище копирует данные в state. Когда хранилище изменяется, данные полностью переписываются. И это лучше, потому что внутри forceUpdate выполняется синхронно, а setState — более эффективный.
Надеюсь, после прочтения этой статьи вам будет проще понять файловую структуру проекта.
Документация Flux содержит несколько полезных примеров, глубоко закопанных внутри.
Если этот пост помог вам понять Flux, то подписывайтесь на меня в твиттере.
Flux-архитектура: определение, применение и отличительные характеристики
Flux — это архитектура, которую разработала команда разработчиков Facebook для работы с фреймворком React. Важно отметить, что Flux не является фреймворком или библиотекой , э то именно архитектура, шаблон или паттерн , по котор ому строятся приложения на React.
React и Flux идут рука об руку. Напомним, что React — это фреймворк на основе JavaScript, который был разработан компанией Фейсбук и используется во фронтенд-разработке. Реакт является достаточно гибким и популярным инструментом. Но что же такое Flux-архитектура?
Flux-архитектура
Большинство пользователей знакомо с таким подходом в программировании, как MVC или Model, View, Controller. При проектировани и приложений по такой модел и React будет отвечать за «V», «View» или «представление». Простыми словами, Реакт будет отвечать за внешний интерфейс приложения. В этом случае Flux будет отвечать за «М» или «Model», что означает шаблон, по которому будет происходить проектирование приложени я и связывание всех его модулей. Однако MVC и Flux — это разные подходы в разработке.
По сути, Flux отвечает за создание слоя данных в JavaScript-приложениях, а также за работу серверной части.
Из чего состоит Flux-архитектура
- «Actions», они же «действия» — это помощники, которые упрощают передачу информации «диспетчеру».
- «Dispatcher», он же «диспетчер» — компонент, который обрабатывает информацию от «actions». По сути , он является «менеджером» приложения, потому что он принимает входящие действия и распределяет их среди разработчиков.
- «Stories», они же «хранилища» — компоненты, которые сохраняют состояни е приложения и его бизнес-логику. Вся работа приложения сосредоточена именно здесь. Внутри «хранилищ» хранятся: информация, методы, функции и обработчики «действий».
- «Controller Views», они же «представления» — это компоненты от Реакт, которые определяют состояние приложения, основываясь на бизнес-логику из «хранилищ».
- В поле «action» возникает какое-то взаимодействие с приложением.
- «Action» вызывает специальные методы, которые задают константы этим взаимодействиям и отправляют их в «диспетчер».
- «Диспетчер» обрабатывает полученное событие. Он различает полученное событие по его источникам. После обработки события он отправляет его ко всем зарегистрированным обработчикам событий, то есть в «хранилище».
- «Хранилище» получает данное событие и определяет ему обработчика. Далее «хранилище» реагирует на полученное событие и создает другое событие «changed», которое определяет состояние приложени я к ак ответ на взаимодействие с ним.
- «Представление» «слушает» «хранилище» и , как только обнаруживает событие «changed» , тут же на него реагирует, обновляя себя и собственные React-компоненты.
Flux-архитектура и React
Flux-архитектура и React отлично зарекомендовали себя в тандеме , э то многократно доказывали разработчики Facebook. Однако оба эти инструмента можно использовать раздельно. React без Flux довольно часто применяетс я х отя бы потому , что не всегда есть необходимость придерживаться строгой архитектурной разработки.
Некоторым пользователям нравится применять Flux-архитектуру, но не нравится работать с React. Что же, и тут есть решение — использовать Flux-фреймворк или фреймворк Delorean. Также можно воспользоваться библиотекой Fluxor, которая поддерживает Flux-архитектуру, но использует более жесткую взаимосвязь между компонентами этой архитектуры.
Заключение
- приложения в такой архитектуре сложнее масштабировать ;
- она сложнее переносит огромную базу ко д а ;
- сложнее управляет кодом приложения ;
- разнообразная реализация потоков данных, что приводит к более сложному поиску возникающих ошибок ;
- более сложная структура ;
- и др.
Мы будем очень благодарны
если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.
Flux: Архитектура приложений на React.js — всестороннее исследование
Flux — это вид архитектуры, которую Facebook использует, чтобы работать с React. А что же такое React?
React — популярная технология для Frontend-разработки, как и Angular — фреймворк для JavaScript, но это только View (Представление) слой, а значит, у нас в распоряжении только V от MVC — Model-View-Controller (Модель — Представление— Контроллер ) архитектуры. React часто упоминается среди других фреймворков, но все, что он дает — это View.
React — это язык шаблонов и всего несколько функций-hook’ов для рендеринга HTML.
Так как он основан на компонентном подходе, вы можете разрабатывать приложение c помощью React просто указывая, как бы вы хотели видеть тот или иной элемент.
React будет автоматически обновлять элемент, когда лежащие в его основе данные изменятся. Главные принципы React: гибкость, эффективность и декларативный код.
Так как React гибкий, то можно использовать один и тот же код в нескольких проектах, создавать на его основе новые приложения и даже использовать в уже существующей базе код без переработок.
Что такое Flux?
Итак, React отвечает за V или View в MVC. А как насчет M или Model части? Flux, шаблон проектирования, соответствует M в MVC.
Это архитектура, ответственная за создание слоя данных в JavaScript приложениях и разработку серверной стороны в веб-приложениях. Flux дополняет составные компоненты вида View в React, используя однонаправленный поток данных.
Также можно сказать, что Flux больше чем шаблон, больше чем фреймворк и имеет 4 главных компонента (более подробно они будут рассмотрены позже):
- Диспетчер (Dispatcher)
- Хранилище (Stores)
- Представления (Views) (React компонент)
- Действие (Action)
Это не похоже на привычный MVС, который мы привыкли видеть в других фреймворках. Действительно, там есть Контроллер, но по большей части это контроллер, отвечающий за Views. Views находятся вверху иерархии, и они передают функционал и данные элементам-потомкам.
Flux следует концепции однонаправленного потока данных, что делает его простым для поиска ошибок. Данные проходят через прямой поток вашего приложения. React и Flux — на данный момент два самых популярных фреймворка, которые используют принцип однонаправленного потока данных.
В то время как React использует виртуальный DOM для отображения изменений, Flux делает это немного иначе. Во Flux, взаимодействие с пользовательским интерфейсом вызовет ряд действий, которые могут изменить данные приложения.
Основные характеристики Flux
Flux имеет открытый исходный код, и это скорее шаблон проектирования, а не фреймворк, поэтому его можно использовать сразу. То, что отличает его от других фреймворков, то отличает его и от шаблона проектирования MVC.
Flux помогает сделать код более предсказуемым, в сравнении с МVC фреймворками. Разработчики могут создавать приложения, не беспокоясь о сложных взаимодействиях между источниками данных.
Flux выгодно отличается более организованным потоком данных — однонаправленным. То, что он однонаправленный, главная особенность Flux. Эти действия распространяются на новую систему в отношении взаимодействия с пользователем.
Также вы можете начать использовать Flux, не используя при этом весь новый код, в отличие от React.
Flux vs. MVC
Итак, у нас есть и MVC и Flux, следующий вопрос, какой из них лучше? Поэтому давайте взглянем на них еще более глубоко.
Есть различные виды MVC паттернов, но главный концепт каждого из них сводится к одному:
Model — поддерживает поведение и данные домена приложения.
View — отображает Model в пользовательском интерфейсе.
Controller — использует пользовательский ввод, управляет Model и View.
Основная проблема с MVC заключается в том, что он недостаточно масштабируется для огромной базы кода Facebook. Flux доказал свою эффективность, потому что все, что связано с Flux, касается тонкой настройки потока внутри приложения.
MVC проверен временем, и даже со времени его запуска 1976 года, он был любимым у многих разработчиков. И даже в последние время разработчики продолжают использовать его для некоторых проектов.
Но MVC не может управлять такой базой кода, как это необходимо Facebook, и тут Flux стал править балом. Давайте посмотрим на главные особенности благодаря которым Flux имеет преимущество над MVC.
The Flow — Flux очень требователен к потоку данных в приложении. Dispatcher данных устанавливает строгие правила и исключения для управления потоком. В MVC нет такой вещи, и потоки реализуются по-разному.
Однонаправленный поток в Flux — в то время как MVC двунаправленный в своем потоке, во Flux все изменения проходят через одно направление, через Dispatcher данных. Store не может быть изменено само по себе, и тот же самый принцип работает для других Actions. Изменения, которые необходимо внести, должны пройти через Dispatcher, через Actions.
Store — в то время как MVC не может моделировать отдельные объекты, Flux может делать это для того, чтоб хранить любые связанные с приложением данные.
Когда встает вопрос о том, что выбрать Flux или MVC, лучше выбрать Flux, потому что его проще понять и работать с минимальным использованием кода. Flux позволяет вам эффективно структурировать ваше приложение, потому что язык программирования React интегрирован с огромной базой кода и чудовищной ран-тайм сложностью, которую мы, разработчики, ненавидим.
Возможно, для того, чтоб понять, почему однонаправленный поток данных является лучшим решение, вам стоит узнать о минусах двунаправленного потока данных.
В двунаправленном потоке данных есть стандартный поток данных — Model-View-Controller. Однако, когда приложения становятся более сложными на Controller ложится слишком большая нагрузка.
Controller берет на себя огромную ответственность, как за сохранения состояния приложения, так и за данные. Кроме того, каскадные обновления делают приложения сложным для понимания и поиска ошибок. В конечном итоге мы имеем приложение, результаты работы которого непредсказуемы.
В однонаправленном потоке данных эта проблема смягчается, и таким образом достигается предсказуемое состояние приложения. Когда поток данных однонаправленен, изменения в слое View влекут за собой изменения в слое данных. Позже эти изменения будут отображены в View. Тем не менее, View не влияет непосредственно на данные приложения.
Архитектура Flux, как она работает
Компоненты архитектуры Flux взаимодействуют между собой скорее как EventBus и менее, чем MVC.
Как упоминалось ранее, Flux это не настоящая библиотека или фреймворк, это новый вид архитектуры, которую Facebook использует для работы с React изнутри. Следовательно, основная функция Flux будет дополнить React и реализовать однонаправленный поток данных.
В стандартной архитектуре Flux следующие компоненты:
- Actions — помощники, которые передают данные в Dispatcher
- Dispatcher — получает эти действия и передает полезную нагрузку зарегистрированным callback-ом.
- Stores — действуют как контейнеры для состояния приложения и логики. Реальная работа приложения происходит в Stores. Stores, зарегистрированные для прослушивания действий Dispatcher, будут соответственно и обновлять View.
- Controller Views — React компоненты захватывают состояние из Stores, а затем передают дочерним компонентам.
Контроллеры в MVC и Flux различаются. Здесь контроллеры являются Controller-View и находятся на самой вершине иерархии. View — это React компоненты.
Весь функционал, как правило, находится в Store. В Store выполняется вся работа и сообщается Dispatcher, какие события/действия он прослушивает.
Когда происходит событие, Dispatcher отправляет “полезную нагрузку” в Store, который зарегестрирован для прослушивания конкретно этого события. Теперь в Store необходимо обновить View, что в свою очередь вызывает действие. Дествие, точно также как и имя, тип события и много другое известны заранее.
View распространяет Action через центральный Dispatcher, и это будет отправлено в различные Stores. Эти Stores будут отображать бизнес-логику приложения и другие данные. Store обновляет все View.
Наиболее хорошо это работает совместно со стилем программирования React, тогда Store отправляет обновления без необходимости подробно описывать, как изменять представления между состояниями.
Это доказывает, что шаблон проектирования Flux следует за однонаправленным потоком данных. Action, Dispatcher, Store и View — независимые узлы с конкретными входными и выходными данными. Данные проходят через Dispatcher, центральный хаб, который в свою очередь управляет всеми данными.
Dispatcher действует как реестр с зарегистрированными callback-ами, на которые отвечают Store. Store будут давать изменения, которые выбраны Controller-Views.
Это происходит, когда View распространяется в системе:
Это доказывает, что во Flux нет двусторонних привязок (bind), что структура сродни функциональному относительному программированию, и еще что-то вроде потокового программирования.
Зависимости, которые происходят в Store, хранятся в строгой иерархии, тогда как Dispatcher обрабатывает обновления. Эта структура также решает проблемы, которые естественным образом возникают при двусторонней привязке.
Для сознания Dispatcher вам необходимо установить Dispatcher из Flux. Сделйте это, напечатав Dispatcher.js.
Детальный обзор Dispatcher
Dispatcher — это глобальный паб или вспомогательный обработчик, который передает полезную нагрузку зарегистрированным callback-ом. Dispatcher может легко устанавливать зависимости между store-ами. Это не похоже на общие pub-sub системы о основном потому что:
- Callback-и не привязаны к конкретным событиям. Каждая полезная нагрузка будет отправлена на каждый зарегистрированный вызов.
- Их возможно частично или полностью отложить до тех пор, пока не будут выполнены все другие обратные вызовы.
Каждый Store регистрирует callback с помощью Dispatcher. Когда поступают новые данные, эти callback-и будут использоваться для отправки данных в Store. Этот процесс вызова callback-ов выполняется с помощью метода dispatch() .
Функция метода dispatch() заключается в том, чтоб предоставить простое, синхронное повторение через callback-и, путем поочередного обращения. По мере того, как приложение становится все сложнее и сложнее, все зависимости в различных хранилищах займут свое место.
Это означает, что, например, когда Store B будет обновляться, автоматически нам нужен Dispatcher для вызова callback-а в Store C. Функция ожидания выполняется при помощи метода waitFor() .
Вот API для работы с Dispatcher:
- register(function callback): string Регистрирует callback для вызова с каждой отправленной полезной нагрузкой. Поставляется с токеном, который будет использоваться waitFor() .
- unregister(string id): void Когда требуется удалить callback (на основании токена).
- (array ids): void Store будет ожидать callback, прежде чем выполнить текущий callback. Это делается только в ответ на отправленную полезную нагрузку.
- dispatch(object payload): void Отправляет полезную нагрузку всем зарегистрированным callback-ам.
- isDispatching(): boolean Будет ли отправляться этот Dispatcher.
Следующие примеры помогут вам разобраться лучше с тем, как работает Dispatcher.
Рассмотрим гипотетическую ситуацию назначения полета, где город выбран по умолчанию, в то время как страна выбирается.
var flightDispatcher = new Dispatcher();// Следит за тем, какая страна выбрана.
var CountryStore = ;// Следит за тем, какой город выбран.
var CityStore = ;// Отслеживает базовую цену полета для выбранного города.
var FlightPriceStore = ;
Когда пользователь изменяет выбранный город, отправляется полезная нагрузка.
flightDispatcher.dispatch( actionType: ‘city-update’,
selectedCity: ‘amsterdam’
>);
Эта полезная нагрузка обрабатывается в CityStore
flightDispatcher.register(function(payload) if (payload.actionType === 'city-update') CityStore.city = payload.selectedCity;
>
>);
Теперь, когда пользователь выбрал страну, отправляется полезная нагрузка:
flightDispatcher.dispatch( actionType: 'country-update',
selectedCountry: 'brazil'
>);
Полезная нагрузка обрабатывается обоими Stores:
CountryStore.dispatchToken = flightDispatcher.register(function(payload) if (payload.actionType === 'country-update') CountryStore.country = payload.selectedCountry;
>
>)
Когда callback обновляет CountryStore это регистрируется, а ссылка сохраняется в возвращенном токене. Используя токен с waitFor() , CountryStore обновляется до callback-а, который обновляет CityStore чтобы запрашивать его данные.
CityStore.dispatchToken = flightDispatcher.register(function(payload) if (payload.actionType === 'country-update') // `CountryStore.country` may not be updated.
flightDispatcher.waitFor([CountryStore.dispatchToken]);
// `CountryStore.country` is now guaranteed to be updated.// Select the default city for the new country
CityStore.city = getDefaultCityForCountry(CountryStore.country);
>
>);
Использование waitFor() можно связать следующим образом:
FlightPriceStore.dispatchToken =
flightDispatcher.register(function(payload) switch (payload.actionType) case 'country-update':
case 'city-update':
flightDispatcher.waitFor([CityStore.dispatchToken]);
FlightPriceStore.price =
getFlightPriceStore(CountryStore.country, CityStore.city);
break;
>
>);
Полезной нагрузке country-update будет гарантировано обращение к зарегистирированным store в следующем порядке: CountryStore , CityStore , и затем FlightPriceStore .
Краткий обзор Flux Utils
Для того, чтобы работать с Flux, вам нужно иметь прочную основу. Это обеспечивается с помощью базовых utility classes (полезных классов). Flux Utils предоставляет их. Тем не менее, они не являются полнофункциональным фреймворком и не способны регулировать все случаи.
Можно положиться на другие Flux-фреймворки, если существующие utility classes не отвечают вашим поставленным задачам. Помня об этом, вы можете просмотреть все главные утилиты.
Для того, чтоб импортировать эти базовые классы flux/utils , следуйте этому коду:
import from 'flux/utils';class CounterStore extends ReduceStore getInitialState(): number return 0;
>reduce(state: number, action: Object): number switch (action.type) case 'increment':
return state + 1;case 'square':
return state * state;default:
return state;
>
>
>
Вот лучшие практики, которые лучше использовать, когда вы работаете с данными классами.
Stores
Его функция кэшировать данные. Поскольку они не имеют публичных сеттеров, они открывают публичные геттеры для доступа к данным. Также функция Store отвечать на действия отправляемые Dispatcher-ом. Stores всегда изменяются, когда меняются данные. Они осуществляют изменения только во время отправки.
Actions
Здесь Actions означают описание действий пользователя, а не действия сеттера; например, select-page , а не set-page-id
Containers
Это React-компоненты, которые контролируют View. Их первичная функция — cобирать информацию со Store и сохранять ее. Контейнеры не имеют props или UI logic.
Views
Это также React-компоненты, но они контролируются Контейнерами. Они имеют UI и представляют логику.
Популярные реализации Flux
Теперь когда у вас есть всеобъемлющее представление о Flux-архитектуре, давайте рассмотрим несколько популярных реализаций Flux.
Redux
Согласно GitHub, Redux предсказуемый State контейнер для JavaScript приложений. Большая часть концепций аналогичны функциональному программированию, а все данные хранятся в одном Store.
Независимо от размера приложения, Redux всегда является единственным объектом, в отличие от Flux, который содержит различные хранилища для разных объектов. Если в данных были сделаны изменения, это не влияет непосредственно на состояние. Таким образом, state является неизменным.
Все обновления и манипуляции выполняются на дереве состояний. Но это не делает приложение медленным, так как данный могут совместно использоваться различными версиями дерева состояний.
Обновления состояния приложения выполняются через Actions, которые представляют собой простые объекты, но содержат свойство типа, изображающее вид выполненных действий. Также сюда включены данные, которые описывают действия.
Dispatcher хранилища отправит действие и оттуда оно отправится в Reducer, а затем в текущее древо состояний. Здесь будут действия приложения будут описаны — различные вещи, которые приложение может делать. Всего лишь одного Reducer’a будет достаточно для трансформации состояний и действий.
Reflux
Одна из популярных реализаций Flux. Но между ними есть некоторые различия. Reflux не использует Dispatcher; вместо этого каждое Action является Dispatcher’ом.
Так как Actions представляют собой функции, нет создателей действий. И, пожалуй, лучшая вещь в Reflux, это то, что он более сжатый и обтекаемый, с гораздо меньшей потребностью повторяющихся кодов.
Fluxxor
Fluxxor Использует ряд инструментов и Flux-архитектуру для создания слоев JS данных. Чтобы полностью насладиться функциональностью Fluxxor, вам нужно будет использовать React для View слоя.
На следующем этапе вы объедините Flux и используете для View. Каждый компонент Fluxxmor представляет собой класс, и вы расширяете отсюда базовые классы.
Alt
Созданная на основе Flux, Alt — библиотека, которая облегчает управление состоянием в JavaScript приложениях. Вы можете установить Alt, если вы используете менеджер пакетов как nom или bower. Alt дает вам все возможности Flux, но использует более простой синтаксис.
Краткое заключение
Основная идея стоящая за использованием Flux-архитектуры — упростить структуру приложения. Это облегчает поддержание и понимание, когда приложение становится более сложным. Поскольку нет никакой двусмысленности во взаимоотношения между различными компонентами, работа становится активной.
Кроме того, Flux является последовательным и повторяемым, что делает работу с ним очень логичной при создании Action. Также проще сделать, чтоб Store знал, как обрабатывать действия, хранить данные и активировать изменение события.
Вы можете быть уверены, что если Facebook использует Flux для разработки кода, то определенно это будет работать и для вашего проекта.