Руководство по Redux для начинающих

Сергей Почекутов

Redux — популярный менеджер состояний в веб-приложениях. Обычно его используют в связке с React, но поддержка не ограничена только этой популярной JS-библиотекой. Можно применять Redux вместе с Angular, Vue и даже ванильным JavaScript.

Redux появился в 2015 году в ответ на экспоненциальный рост сложности интерфейсных приложений. Он объединил подход Flux (однонаправленный поток данных со специфическими событиями и слушателями) с функциональным программированием и за короткий срок превратился в одну их самых популярных интерфейсных архитектур.

Предназначение Redux — управление состоянием приложений. В основе библиотеки лежат несколько концепций, которые вы изучите в этом руководстве для начинающих.

Содержание:
1. Когда нужно пользоваться Redux
2. Основные концепции
3. Базовая структура Redux
4. Поток данных
5. Установка и начало работы
Заключение

1. Когда нужно пользоваться Redux

В простых проектах Redux не нужен. Но если в приложении несколько компонентов, которым необходимо совместно использовать одно и то же состояние, при этом сами компоненты расположены в разных частях приложения, то без Redux управлять состоянием будет сложно.

Еще одна мотивация использовать Redux — замена стандартных механизмов локального хранилища. Например, в React хранилище изолировано. Если нужно передавать состояние между компонентами, то приходится использовать пропсы либо поднимать его наверх до ближайшего «родителя». Redux устраняет эту проблему.

2. Основные концепции

Основная идея 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) — чистая функция, которая берет предыдущее состояние и переданное действие, а затем на их основе вычисляет новое состояние. Как и любые другие функции, редукторы можно разбивать на более мелкие или делать переиспользуемыми.

Редуктор НЕ должен менять аргументы и само состояние. Он каждый раз создает новое состояние. Работа чистой функции также не должна вызывать побочных эффектов и вызова нечистых функций — тех, результат которых зависит от чего-то еще, кроме их аргументов.

Чем сложнее приложение, тем больше редукторов может применяться к одному действию.

3. Базовая структура Redux

Используя основные концепции 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), 
  } 
}

4. Поток данных

Чтобы лучше понимать, как работает поток данных в 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>
  )
}

Приложение состоит из трех частей:

  1. State — источник состояния, который управляет приложением.
  2. View — декларативное описание интерфейса на основе текущего состояния.
  3. Action — события, которые происходят в приложении после пользовательского ввода и инициируют изменение состояния.

В Redux используется односторонний поток данных — наследство подхода Flux. Пошагово его можно описать следующим образом:

  • Состояние описывает приложение в текущий момент времени.
  • На основе состояния отображается пользовательский интерфейс.
  • Когда происходит действие, состояние обновляется.
  • Пользовательский интерфейс отображает обновленное состояние.

Простой пример со счетчиком и кнопкой. Счетчик показывает 0 — это текущее состояние, которое отображается в интерфейсе. Пользователь нажал на кнопку и таким образом отправил действие. Состояние изменилось — с 0 на 1. Теперь в интерфейсе отображается обновленное состояние: счетчик показывает 1.

Для Redux этот же пример можно описать более подробно. Так происходит начальная настройка состояния:

  • Хранилище создается с использованием корневого редуктора.
  • Хранилище вызывает корневой редуктор и сохраняет возвращенное значение как начальное.
  • При первом отображении пользовательского интерфейса его компоненты получают доступ к текущему состоянию. Они используют эти данные, чтобы понять, что показать пользователю. В то же время компоненты подписываются на все будущие изменения, чтобы вовремя их обнаружить и отобразить.

А вот как проходят все изменения состояния:

  1. В приложении что-то происходит — например, пользователь нажимает кнопку.
  2. Приложение отправляет action в хранилище Redux с помощью функции dispatch({type: 'counter/incremented'}).
  3. Хранилище запускает функцию редуктора с предыдущим состоянием и полученным действием. Возвращаемое значение хранилище сохраняет как новое состояние.
  4. Хранилище уведомляет все подписанные на изменения части пользовательского интерфейса о новом состоянии.
  5. Каждый компонент проверяет, не изменились ли нужные ему части.
  6. Каждый компонент, которому нужны измененные части, вызывает повторную отрисовку с новыми данными, чтобы отобразить их на экране.

Несмотря на такое пространное описание, концепция однонаправленного потока данных проста. Все действия передаются через dispatch() в хранилище, где редуктор генерирует новое состояние. Затем хранилище уведомляет всех слушателей. И так по кругу.

5. Установка и начало работы

Установить 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 и изучили его основные концепции. Чтобы закрепить знания, посмотрите эти курсы на русском и английском языках:

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023