Redux — популярный менеджер состояний в веб-приложениях. Обычно его используют в связке с React, но поддержка не ограничена только этой популярной JS-библиотекой. Можно применять Redux вместе с Angular, Vue и даже ванильным JavaScript.
Redux появился в 2015 году в ответ на экспоненциальный рост сложности интерфейсных приложений. Он объединил подход Flux (однонаправленный поток данных со специфическими событиями и слушателями) с функциональным программированием и за короткий срок превратился в одну их самых популярных интерфейсных архитектур.
Предназначение Redux — управление состоянием приложений. В основе библиотеки лежат несколько концепций, которые вы изучите в этом руководстве для начинающих.
Содержание:
1. Когда нужно пользоваться Redux
2. Основные концепции
3. Базовая структура Redux
4. Поток данных
5. Установка и начало работы
Заключение
В простых проектах Redux не нужен. Но если в приложении несколько компонентов, которым необходимо совместно использовать одно и то же состояние, при этом сами компоненты расположены в разных частях приложения, то без Redux управлять состоянием будет сложно.
Еще одна мотивация использовать Redux — замена стандартных механизмов локального хранилища. Например, в React хранилище изолировано. Если нужно передавать состояние между компонентами, то приходится использовать пропсы либо поднимать его наверх до ближайшего «родителя». Redux устраняет эту проблему.
Основная идея Redux — создать централизованное место для хранения глобального состояния приложения. Для достижения этой цели используются три основные концепции.
Глобальное состояние приложения (state
) хранится в виде объекта внутри одного хранилища (store
). Любой фрагмент данных в момент может существовать только в одном месте и не может дублироваться в других местах.
Такой подход упрощает отладку и проверку состояния приложения по мере его изменения, а также централизует логику, которая взаимодействует со всем приложением.
Состояние приложения содержится в хранилище. Отобразить его можно вызовом функции getState()
. Для обновления используется функция dispatch()
. Зарегистрироваться в качестве слушателя состояния или удалиться помогает функция subscribe()
.
Хранилище всегда уникально. Вот пример его реализации:
import { createStore } from 'redux' import listManager from './reducers' let store = createStore(listManager)
Можно также инициировать хранилище через серверные данные:
let store = createStore(listManager, preexistingState)
Примеры применения функций:
store.getState() // Получаем состояние store.dispatch(addItem('Something')) // Обновляем состояние const unsubscribe = store.subscribe(() => const newState = store.getState() ) unsubscribe() // Слушаем изменения состояния
Общее состояние приложения представлено объектом JS. Неизменяемое дерево состояний доступно исключительно для чтения. Единственный способ внести изменения — отправить action
(действие), объект JS, который описывает, что произошло.
Благодаря такому подходу пользовательский интерфейс не перезаписывает данные случайно. Разработчику проще отследить, почему состояние обновилось. Поскольку действия являются объектами JS, их можно регистрировать, сериализировать, сохранять и воспроизводить для отладки и тестирования.
Пример действия:
{ type: 'CLICKED_LINK' } // подробности об изменении { type: 'SELECTED_USER', userId: 285 }
Единственное требование к action
— добавление свойства type
, значением которого обычно является строка. По мере разрастания приложений строки в типах действий заменяют константами, а затем выносят в отдельные файлы и импортируют. Это упрощает внесение изменений и дальнейшее масштабирование проекта.
Например, так могут выглядеть константы:
const ADD_DATA = 'ADD_DATA' const action = { type: ADD_DATA, title: 'Important data' }
Затем эти константы можно импортировать в другой файл:
import { ADD_DATA, UPDATE_DATA } from './actions'
Создавать действия помогают функции-генераторы.
Пример реализации:
function adddata(some) { return { type: ADD_DATA, title: some } }
Обычно они инициируются вместе с dispatch
— функцией отправки действия.
dispatch(adddata('Highload'))
Но можно инициировать их и при определении функции отправки:
const dispatchAdddata = i => dispatch(adddata(i)) dispatchAdddata('Highload')
Когда вы отправляете action
, что-то случается и состояние приложения меняется. За эту часть работы отвечают редукторы.
Редуктор (reducer
) — чистая функция, которая берет предыдущее состояние и переданное действие, а затем на их основе вычисляет новое состояние. Как и любые другие функции, редукторы можно разбивать на более мелкие или делать переиспользуемыми.
Редуктор НЕ должен менять аргументы и само состояние. Он каждый раз создает новое состояние. Работа чистой функции также не должна вызывать побочных эффектов и вызова нечистых функций — тех, результат которых зависит от чего-то еще, кроме их аргументов.
Чем сложнее приложение, тем больше редукторов может применяться к одному действию.
Используя основные концепции Redux, можно представить такую базовую структуру:
Состояние:
{ list: [ { title: "First" }, { title: "Second" }, ], title: 'List' }
Действия:
{ type: 'ADD_DATA', title: 'Third' } { type: 'REMOVE_DATA', index: 1 } { type: 'CHANGE_LIST_TITLE', title: 'Lis twot' }
Редуктор для каждой части состояния:
const title = (state = '', action) => { if (action.type === 'CHANGE_LIST_TITLE') { return action.title } else { return state } } const list = (state = [], action) => { switch (action.type) { case 'ADD_DATA': return state.concat([{ title: action.title }]) case 'REMOVE_DATA': return state.map((item, index) => action.index === index ? { title: item.title } : item default: return state } }
Редуктор для общего состояния:
const listManager = (state = {}, action) => { return { title: title(state.title, action), list: list(state.list, action), } }
Чтобы лучше понимать, как работает поток данных в Redux, возьмем простой пример компонента React. Пусть это будет счетчик, который отслеживает число и увеличивает его при нажатии на кнопку.
function Counter() { const [counter, setCounter] = useState(0) const increment = () => { setCounter(prevCounter => prevCounter + 1) } return ( <div> Value: {counter} <button onClick={increment}>Increment</button> </div> ) }
Приложение состоит из трех частей:
State
— источник состояния, который управляет приложением.View
— декларативное описание интерфейса на основе текущего состояния.Action
— события, которые происходят в приложении после пользовательского ввода и инициируют изменение состояния.В Redux используется односторонний поток данных — наследство подхода Flux. Пошагово его можно описать следующим образом:
Простой пример со счетчиком и кнопкой. Счетчик показывает 0 — это текущее состояние, которое отображается в интерфейсе. Пользователь нажал на кнопку и таким образом отправил действие. Состояние изменилось — с 0 на 1. Теперь в интерфейсе отображается обновленное состояние: счетчик показывает 1.
Для Redux этот же пример можно описать более подробно. Так происходит начальная настройка состояния:
А вот как проходят все изменения состояния:
action
в хранилище Redux с помощью функции dispatch({type: 'counter/incremented'})
.Несмотря на такое пространное описание, концепция однонаправленного потока данных проста. Все действия передаются через dispatch()
в хранилище, где редуктор генерирует новое состояние. Затем хранилище уведомляет всех слушателей. И так по кругу.
Установить Redux можно через NPM или YARN:
npm install redux yarn add redux
Redux не знает, как вы структурируете приложение. Есть несколько популярных шаблонов. Но на старте лучше выбрать один подход и придерживаться его, пока не разберетесь до конца, как части приложения взаимодействуют друг с другом.
Для обучения можно использовать самую простую структуру — создать папку store
и поместить внутри нее все, что связано с Redux и хранилищем. Пример такой структуры:
.store ├── actionCreators │ ├── action_1.js │ └── action_2.js ├── actions │ ├── action_1.js │ └── action_2.js ├── reducers │ ├── reducer_1.js │ ├── reducer_2.js │ └── rootReducer.js ├── initialState.js └── store.js
Один из самых распространенных шаблонов — структура в стиле Rails. В ней используется несколько каталогов верхнего уровня. Например, такой может быть структура для проекта на React + Redux:
Components
— для хранения компонентов React. Здесь не нужна связь с Redux.Containers
— папка с компонентами Smart React, которые отправляют действия в Redux. Здесь настраивается связь React и Redux.Actions
— каталог с генераторами действий.Reducers
— папка с отдельными файлами для каждого редуктора.Store
— каталог с настройками хранилища и логикой инициализации состояния.Этот шаблон подходит для приложений небольшого и среднего размера. На больших проектах может быть удобнее использовать стиль домена или аналогичный. В таком случае у каждой функции будет свой каталог (домен), внутри которого будет храниться все, связанное с этой функцией.
В этом руководстве вы познакомились с Redux и изучили его основные концепции. Чтобы закрепить знания, посмотрите эти курсы на русском и английском языках:
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…