Reflux представляет собой решение, суть которого упростить создание архитектуры приложения на React по сравнению с использованием Flux. В каком-то роде Reflux может рассматриваться как замена Flux, которая упрощает взаимодействие между различными частями приложения.
Основу Reflux составляют три компонента:
Действие (action) содержит определения всех применяемых действий в приложении
Компонент представления (view component) - собственно визуальная часть приложения, в роли которого обычно используется объект Reflux.Component. Класс Reflux.Component является расширением стандартного класса React.Component
Хранилище (store) хранит данные и логику приложения
В целом мы получаем ту же самую архитектуру, что и во Flux, только без диспетчера:
В этой схеме с помощью действий компонент представлений направляет в хранилище команду. Получив команду, хранилище изменяет свое состояние, что приводит к обновлению компонента представлений.
Рассмотрим на примере. Создадим новый проект. Для этого определим каталог refluxapp. Сначала добавим в проект новый файл package.json:
{ "name": "refluxapp", "description": "A React.js project using Reflux", "version": "1.0.0", "author": "metanit.com", "scripts": { "dev": "webpack-dev-server --hot --open", "build": "webpack" }, "dependencies": { "react": "^16.5.0", "react-dom": "^16.5.0", "reflux": "^6.4.1" }, "devDependencies": { "@babel/core": "^7.1.2", "babel-loader": "^8.0.0", "@babel/preset-env": "^7.1.0", "@babel/preset-react":"^7.0.0", "webpack": "^4.20.0", "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.1.9" } }
Чтобы использовать Reflux, здесь определяется зависимость "reflux". Кроме того, поскольку данный проект будет разделен на модули, то для их компиляции и сборки будут использоваться пакеты babel и webpack.
Также добавим в каталог проекта главную страницу приложения index.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Reflux в React</title> </head> <body> <div id="container"></div> <script src="public/bundle.js"></script> </body> </html>
Для файлов приложения определим в проекте новую папку app. Итак, наше приложение будет очень простым: оно будет выводить список объектов с возможностью добавления и удаления данных.
Вначале добавим определение действий. Для этого определим в папке app новый файл actions.jsx со следующим содержимым:
var Reflux = require("reflux"); var Actions = Reflux.createActions(["addItem", "removeItem"]); module.exports = Actions;
Итак, здесь подключаем функциональность Reflux и с помощью метода Reflux.createActions() создаем набор действий. В нашем случае это будут действия добавления и удаления элемента списка.
Далее определим хранилище. Для этого добавим в папку app новый файл phonestore.jsx:
var Reflux = require("reflux"); var Actions = require("./actions.jsx"); class PhonesStore extends Reflux.Store { constructor() { super(); this.state = {phones: ["iPhone 7", "Samsung Galaxy S8"]}; this.listenTo(Actions.addItem, this.onAddItem); this.listenTo(Actions.removeItem, this.onRemoveItem); } onAddItem(phone){ this.state.phones.push(phone); } onRemoveItem(phone){ var data = this.state.phones; var index = data.indexOf(phone); if (index > -1) { data.splice(index, 1); this.setState({phones: data}); } } } module.exports = PhonesStore;
Хранилище наследуется от класса Reflux.Store. В нем также, как и в стандартных компонентах React, мы можем определить состояние state. В данном случае в хранилище определен массив phones - тот список, который будет выводиться на веб-страницу.
Кроме того, в конструкторе хранилища связываем действия с методами хранилища:
this.listenTo(Actions.addItem, this.onAddItem); this.listenTo(Actions.removeItem, this.onRemoveItem);
То есть тем самым мы указываем, какой метод будет вызываться при получении хранилищем той или иной команды. Так, при получении команды Actions.addItem будет
вызываться метод onAddItem()
, который будет добавлять новый элемент в массив.
При получении команды Actions.removeItem будет происходить удаление указанного элемента из массива.
Теперь добавим в папку app новый файл phoneslist.jsx, который будет содержать визуальные компоненты:
var React = require("react"); var Reflux = require("reflux"); var Actions = require("./actions.jsx"); var PhonesStore = require("./phonestore.jsx"); class PhonesList extends Reflux.Component{ constructor(props){ super(props); this.state = {newItem: ""}; this.store = PhonesStore; this.onInputChange = this.onInputChange.bind(this); this.onClick = this.onClick.bind(this); } onInputChange(e){ this.setState({newItem:e.target.value}); } onClick(e){ if(this.state.newItem){ Actions.addItem(this.state.newItem); this.setState({newItem:" "}); } } onRemove(item){ if(item){ Actions.removeItem(item); } } render(){ var remove = this.onRemove; return <div> <input type="text" value={this.state.newItem} onChange={this.onInputChange} /> <button onClick={this.onClick}>Добавить</button> <h2>Список смартфонов</h2> <div> { this.state.phones.map(function(item){ return <Phone key={item} text={item} onRemove={remove} /> }) } </div> </div>; } } class Phone extends React.Component{ constructor(props){ super(props); this.state = {text: props.text}; this.onClick = this.onClick.bind(this); } onClick(e){ this.props.onRemove(this.state.text); } render(){ return <div> <p> <b>{this.state.text}</b><br /> <button onClick={this.onClick}>Удалить</button> </p> </div>; } } module.exports = PhonesList;
Главным компонентом является PhonesList. Он наследуется от класса Reflux.Component. Reflux.Component расширяет стандартный класс React.Component, поэтому здесь мы можем работать как со стандартным компонентом. Однако необязательно наследовать все компоненты от Reflux.Component.
В конструкторе компонента с помощью свойства this.store определяется хранилище, с которым связан компонент. Если вдруг у нас несколько хранилищ, то мы могли бы определить другое свойство - this.stores, которое принимает массив хранилищ.
Для отслеживания добавляемого элемента в состоянии компонента определяется объект this.state.newItem.
При нажатии на кнопку на форме добавления срабатывает обработчик onClick()
, в котором вызывается действие Actions.addItem. А при выполнении
метода onRemove()
будет вызываться действие Actions.removeItem. Таким образом, посредством действий из компонента мы можем направлять команду хранилищу.
И также в папке app определим файл app.jsx, который будет содержать логику установки компонента на веб-страницу:
var ReactDOM = require("react-dom"); var React = require("react"); var PhonesList = require("./phoneslist.jsx"); ReactDOM.render( <PhonesList />, document.getElementById("container") )
И в конце, так как мы будем использовать Webpack, добавим в корневой каталог проекта файл webpack.config.js:
var path = require('path'); module.exports = { entry: "./app/app.jsx", // входная точка - исходный файл output:{ path: path.resolve(__dirname, './public'), // путь к каталогу выходных файлов - папка public publicPath: '/public/', filename: "bundle.js" // название создаваемого файла }, module:{ rules:[ //загрузчик для jsx { test: /\.jsx?$/, // определяем тип файлов exclude: /(node_modules)/, // исключаем из обработки папку node_modules loader: "babel-loader", // определяем загрузчик options:{ presets:["@babel/preset-env", "@babel/preset-react"] // используемые плагины } } ] } }
В итоге весь проект будет выглядеть следующим образом:
Перейдем в терминале/командной строке к местоположению проекта и для установки пакетов выполним команду:
npm install
Затем для компиляции и упаковки файлов вызовем команду:
npm run build
Эта команда сгенерирует файл public/bundle.js, который будет подключаться на веб-страницу.
И в конце запустим проект на выполнение командой npm run dev: