Алгоритм работы с React & Redux – с чего начать?
Redux представляет собой контейнер предсказуемого состояния данных для JavaScript, что позволяет создавать надежные и легко расширяемые приложения, которые будут работать в различных окружениях исполнения.
В данной статье мы рассмотрим простой алгоритм подключения Redux к React-приложению, и последующей работы с ним.
Мы разберем каким образом подключить Redux к приложению, а также – где, когда и в какой последовательности использовать Actions, Reducers и Dispatchers.
Для простоты понимания, сначала мы произведем все необходимые действия в одном JavaScript файле, затем разобьём получившийся код на несколько файлов, т.е. создадим начальную структуру приложения. И в конце всего подключим полезный пакет react-redux, который позволит значительно упростить работу с Redux.
Итак, начнём!
Часть 1
1. Подготовка
В большинстве туториалах по использованию React & Redux предварительно создается довольно-таки большая структура приложения, с применением сборки приложения и различных дополнительных инструментов. Все это может создать (и создаст, поверьте) трудности для понимания работы Redux, принцип которой весьма прост. Чтобы избежать ненужных трудностей, мы будем использовать минимально необходимый набор инструментов для работы нашего приложения.
Стартовый шаблон приложения можно скачать по ссылки из репозитория проекта.
Разберем более подробно необходимый минимум для работы нашего приложения.
В файле package.json
содержится несколько пакетов, которые обеспечат корректное функционирование приложения:
babel-preset-react
– преобразует код JSX в обычный JSbabel-preset-es2015
– преобразует синтаксис ES6 в ES5babelify
&watchify
– вспомогательные тулзы для наблюдения за файлами и работы babellive-server
– простой сервер для нашего приложения с автообновлениемconcurrently
– одновременный запуск нескольких npm команд
Ну и самое главное, в файле src/index.html
мы просто подключим библиотеки React & Redux по ссылке – всё как в старые добрые времена 🙂
После того, как стартовый шаблон скачан из репозитория, необходимо выполнить установку пакетов командой npm install
.
Добавим в файл src/app.js
начальный код для проверки работоспособности нашего приложения:
const App = (props) => {
return (
<div className='app'>
<h1>Привет, React & Redux!</h1>
</div>
)
};
ReactDOM.render(
<App></App>,
document.getElementById('root')
);
Запустим приложение командой npm start
, и если все было установлено правильно, то в браузере появится приветствие, созданное с помощью React.
2. Создаем Store
Первым делом создадим хранилище, которое будет общим для всего приложения:
const store = Redux.createStore(Redux.combineReducers({
posts
}));
Специально для целей объединения нескольких редукторов, мы используем метод Redux .combineReducers()
, который произведет все необходимые действия.
3. Создаем Reducer
Затем создадим редуктор posts
:
const posts = (state, action) => {
switch(action.type) {
case 'ADD_POST':
let id = +new Date; // bad practice :)
// Формируем новый массив объектов, в котором будут объединены все необходимые нам данные
// Возвращаем новое состояние данных приложения в нужном нам формате
return [...state, Object.assign({}, action.text, {id})];
default:
return state || [];
}
};
Каждый редуктор принимает следующие параметры:
state
– состояние данных (posts) приложения в момент обращения к редукторуaction
– действие, при котором происходит обращение к редуктору
И возвращает или новое состояние данных, или состояние данных приложения по умолчанию.
4. Создаем Action Creators
Создадим Action Creators с именем addPost
и действием ADD_POST
:
const addPost = text => ({
type: 'ADD_POST',
text
});
Action Creators вернет объект с типом действия и любыми дополнительными данными, которые нам нужны.
5. Создаем дочерний компонент
Дочерний компонент нам понадобится для наглядной демонстрации Dispatchers и работы с действиями. Для начала изменим основной компонент, чтобы он смог вывести дочерний:
const App = (props) => {
return (
<div className='app'>
{/* Вывод дочерних компонентов */}
{props.children}
</div>
)
};
Затем создадим дочерний компонент, который, к примеру, будет отвечать за вывод постов:
class Content extends React.Component {
render() {
let {posts} = this.props; // destruction нужных нам свойств из объекта props
return (
<div className='content'>
<h2>Все посты</h2>
<ul>
{posts.map((post, i) =>
<li key={i}>{post.text}</li>
)}
</ul>
<input ref='add' onKeyPress={this.createPost.bind(this)} type='text' />
</div>
)
}
createPost(event) {
// Если это не клавиша enter, то прерываем работу
if(event.which !== 13) return;
let text = ReactDOM.findDOMNode(this.refs.add).value;
this.props.addPost({text});
}
}
6. Запускаем приложение
Данное решение является временным, в третьей части, когда мы подключим пакет react-redux
, мы избавимся от функции
run()
:
function run() {
// Получаем данные, которые передадим в компонент Content
let state = store.getState();
ReactDOM.render(
<App>
<Content
posts={state.posts}
addPost={text => store.dispatch(addPost(text))}
/>
</App>,
document.getElementById('root')
);
}
run();
// Подписываемся на изменения store, чтобы запускать run()
store.subscribe(run);
В данном случае, при каждом изменении store, будет запускаться run()
функция и в компонент
Content
передадутся актуальные данные.
Сейчас наш дочерний компонент Content
не имеет прямого доступа к store. Поэтому мы передаем в него свойство
addPost
, которое, по сути, является функций Dispatcher для инициализации Action Creator
addPost
. В самом же дочернем компоненте, мы вызываем этот Dispatcher в методе
createPost()
:
this.props.addPost({text});
И передаем в него объект со свойством text
, значение которого берется из текстового поля.
Последовательность такова:
Текстовое поле -> onKeyPress -> createPost -> this.props.addPost({text}) -> text => store.dispatch(addPost(text))
Код проекта на текущий момент можно посмотреть по ссылке: https://github.com/sbogdanov108/react-redux-algorithm/tree/01-Part-one.
Часть 2
7. Разбиваем приложение на файлы
Для начала уберем из index.html
подключение React и Redux:
<!-- React -->
<script src="https://unpkg.com/react@15/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.js"></script>
<!-- Redux -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.js"></script>
Затем установим с помощью npm пакеты react
, react-dom
и redux
:
npm i --save react react-dom redux
Создадим файл src/actions.js
и поместим в него код Action Creators из файла app.js.
Сейчас это только одна функция
addPost()
:
export const addPost = text => ({
...
});
Теперь переместим редуктор в файл src/reducers.js
:
export const posts = (state, action) => {
switch(action.type) {
...
}
};
Далее нам нужно поместить компоненты в разные файлы. Начнем с компонента App
. Создадим файл
src/components/App.js
и поместим в него код нашего компонента:
import React from 'react';
const App = (props) => {
...
};
export default App;
И дочерний компонент переместим в файл src/components/Content.js
:
import React from 'react';
import ReactDOM from 'react-dom';
class Content extends React.Component {
...
}
export default Content;
И конечно же не забудем подключить созданные файлы и нужные модули в файле src/app.js
:
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, combineReducers} from 'redux';
/*
* Action creators
* */
import {addPost} from './actions';
/*
* Reducers
* */
import * as reducers from './reducers';
/*
* Store
* */
const store = createStore(combineReducers(reducers));
/*
* Главный компонент
* */
import App from './components/App';
/*
* Дочерний компонент
* */
import Content from './components/Content';
// Далее без изменений...
Текущее состояние проекта можно посмотреть по ссылке: https://github.com/sbogdanov108/react-redux-algorithm/tree/02-Part-two.
Часть 3
8. Подключаем пакет react-redux
По своей сути, react-redux
предоставляет удобную реализацию доступа компонентам к store и работы с actions.
Для начала установим этот пакет:
npm i --save react-redux
Теперь в файле src/app.js
подключим этот пакет и обернем компонент нашего приложения специальным компонентом
Provider
, который предоставляется react-redux
:
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, combineReducers} from 'redux';
import {Provider} from 'react-redux';
import * as reducers from './reducers';
import App from './components/App';
import Content from './components/Content';
const store = createStore(combineReducers(reducers));
ReactDOM.render(
<Provider store={store}>
<App>
<Content />
</App>
</Provider>,
document.getElementById('root')
);
В качестве параметра, компонент Provider
принимает наше store.
Теперь дочерний компонент Content
может получить прямой доступ к store.
9. Импортируем нужные модули
В файле src/component/Content.js
внесем изменения:
import {connect} from 'react-redux';
import {addPost} from '../actions';
Метод connect()
поможет определить, какие свойства и действия будут у нашего компонента, а также связать компонент и store.
Также мы импортировали те действия, которые относятся к нашему компоненту.
10. Определяем свойства и действия для компонента
// state destruction
const mapStateToProps = ({posts}) => ({
posts
});
const mapDispatchToProps = (dispatch) => ({
addPost: text => dispatch(addPost(text))
});
Функция mapStateToProps()
определяет, какие свойства будут связаны между store и компонентом.
Функция mapDispatchToProps()
определяет, какие действия будут доступны компоненту.
11. Связываем всё вместе
С помощью метода connect()
связываем свойства и действия с нашим компонентом:
export default connect(mapStateToProps, mapDispatchToProps)(Content);
Метод connect()
определяет, какие свойства и действия будут у нашего компонента.
Конечное состояние проекта можно посмотреть по ссылке: https://github.com/sbogdanov108/react-redux-algorithm.
12. Завершение
Итак, настало время подвести итог.
Краткий алгоритм работы с React & Redux будет следующим:
- Cоздаем:
- Store
- Reducers
- Action Creators
- Подключаем:
- Пакет
react-redux
- Оборачиваем приложение компонентом
Provider
из этого пакета - Передаем в
Provider
наше store
- Пакет
- В компоненте, где нужен доступ к store подключаем:
- Метод
connect()
из пакетаreact-redux
- Созданные нами Action Creators
- Метод
- Создаем функции:
mapStateToProps()
– для свойствmapDispatchToProps()
– для действий
- Связываем:
- С помощью метода
connect()
наш компонент и созданные выше две функции
- С помощью метода
Готово!
При создании статьи были использованы следующие источники: