Redux руководство на русском

Redux-in-russian

Оригинальная документация по Redux с переводом на русский

Redux Logo

Redux — это предсказуемое хранилище состояния для JavaScript приложений.
(Не путайте с WordPress фреймворком – Redux Framework.)

Он помогает вам писать приложения, которые ведут себя предсказуемо, работают в разных окружениях (клиентских, серверных и нативные приложения) и которые легко тестировать. Кроме того, он предоставляет отличные возможности для разработчиков, такие как редактирование кода в реальном времени в сочетании с отладчиком путешествий во времени.

Вы можете использовать Redux вместе с React или с любой другой view-библиотекой.
Это крошечная библиотека (2kB, включая зависимости).

GitHub Workflow Status
npm version
npm downloads
redux channel on discord
Changelog #187

Установка

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

npm install @reduxjs/toolkit react-redux

Для самой библиотеки Redux:

Для более подробной информации, смотри страницу установки.

Документация

Документация по Redux доступна по адресу https://redux.js.org:

  • Введение
  • Учебники
  • Руководства по использованию
  • FAQ
  • Справочник по API

Изучите Redux

Учебное пособие по основам Redux

Redux Essentials tutorial — это пособие по основам, которое учит вас, как использовать Redux правильно, используя наши последние рекомендуемые API и лучшие практики. Мы рекомендуем начать с этого.

Базовое руководство по Redux

Redux Fundamentals tutorial — это учебник «снизу вверх», который учит, «как работает Redux» начиная с первых принципов и без каких-либо абстракций и почему существуют стандартные шаблоны использования Redux.

Дополнительные учебные пособия

  • Репозиторий Redux, содержит несколько примеров проектов, демонстрирующих различные аспекты использования Redux. Почти во всех примерах есть соответствующая песочница CodeSandbox. Это интерактивная версия кода, которую можно попробовать онлайн. Смотрите полный список на странице Примеров.
  • видеокурсы на Egghead.io, от создателя Redux Dan Abramov бесплатный «Getting Started with Redux» и Building React Applications with Idiomatic Redux
  • от разработчика Redux Mark Erikson — доклад с конференции «Redux Fundamentals» и слайды воркшопа «Redux Fundamentals»
  • Пост Dave Ceddia A Complete React Redux Tutorial for Beginners

Другие ресурсы

  • Redux FAQ отвечает на многие распространенные вопросы о том, как использовать Redux и Использование Redux содержит информацию об обработке полученных данных, тестировании, структурировании логики редьюсеров и сокращении шаблонов.
  • Серия учебных пособий от разработчика Redux Mark Erikson Practical Redux демонстрирует реальные промежуточные и продвинутые методы работы с React и Redux (также доступны в качества интерактивного курса на Educative.io).
  • React/Redux links list** содержит категоризированные статьи о работе с редьюсрами и селекторами, управлению сайд-эффектами, архитектуре Redux architecture и лучшим практиками и т.д.
  • Наше сообщество создало тысячи связанных с Redux библиотек, надстроек и инструментов. Экосистема перечисляет наши рекомендации, а также есть полный список, доступный в Redux addons catalog.

Помощь и обсуждения

Канал #redux как часть Reactiflux Discord community, наш официальный ресурс для всех вопросов связанных с изучением и использованием Redux. Reactiflux — отличное место для того, чтобы общаться, задавать вопросы и учиться — присоединяйтесь к нам!

Прежде чем продолжить

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

Вот несколько советов о том, когда имеет смысл использовать Redux:

  • У вас есть обоснованные объемы данных, меняющихся со временем
  • Вам нужен один источник информации для вашего состояния
  • Вы приходите к выводу, что сохранять все ваше состояние в компоненте верхнего уровня уже недостаточно

Да, эти рекомендации являются субъективными и расплывчатыми, но это справедливо. Момент, в который вы должны интегрировать Redux в ваше приложение, отличается для каждого пользователя и каждого приложения.

Дополнительные сведения о том, как использовать Redux, см:

  • When (and when not) to reach for Redux
  • You Might Not Need Redux
  • The Tao of Redux, Part 1 — Implementation and Intent
  • The Tao of Redux, Part 2 — Practice and Philosophy
  • Redux FAQ

Опыт разработки

Дэн Абрамов, автор Redux, написал Redux пока работал над своим докладом на React Europe, который назывался “Hot Reloading with Time Travel”. Его целью было создание библиотеки управления состоянием с минимальным API, но вполне предсказуемым поведением, так чтобы можно было реализовать протоколирование, горячую перезагрузку, путешествия во времени, универсальные приложения, запись и воспроизведение, без каких-либо вложений от разработчика.

Влияния

Redux развивает идеи Flux, но избегает его сложности, воспользовавшись примерами из Elm.
Даже если вы не использовали Flux или Elm, для начала работы с Redux потребуется всего несколько минут.

Основные принципы

Все состояние вашего приложения сохранено в объекте внутри одного стора (store).
Единственный способ изменить дерево состояния — это вызвать экшен (action)*, объект описывающий то, что случилось.
Чтобы указать, каким образом экшены преобразовывают дерево состояния, вы пишете чистые «редьюсеры».

Все, готово!

import { createStore } from 'redux'

/**
 * Это редьюсер - чистая функция в формате (state, action) => state.
 * Он описывает то, как экшен преобразовывает состояние в следующее состояние 
 *
 * Структура состояния зависит от вас: это может быть примитивом,
 * массивом, объектом или даже структурой данных Immutable.js.
 * Важно только одно, вы не должны изменять объект состояния
 * напрямую, а возвращать новый объект, если состояние изменилось
 *
 * В этом примере мы используем `switch` и строки, но вы можете
 * использовать хелпер, который следует другому соглашению
 * (например, function maps), если это имеет смысл для вашего
 * проекта.
 */
function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// Создаем Redux стор, который хранит состояние вашего приложения.
// Его API - { subscribe, dispatch, getState }.
let store = createStore(counter)

// Вы можете использовать subscribe() для обновления UI в ответ на изменения состояния.
// Обычно вы должны использовать библиотеку привязки (например, React Redux), а не использовать subscribe() напрямую.
// Однако также может быть полезно сохранить текущее состояние в localStorage.
store.subscribe(() => console.log(store.getState()))


// Единственный способ изменить внутреннее состояние - это вызвать экшен
// Экшены могут быть сериализированы, залогированы или сохранены и далее воспроизведены.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1

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

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

Примеры

Почти все примеры имеют соответствующую песочницу CodeSandbox. Это интерактивная версия кода, с которой вы можете играть онлайн.

  • Counter Vanilla: Source
  • Counter: Source | Sandbox
  • Todos: Source | Sandbox
  • Todos with Undo: Source | Sandbox
  • Todos w/ Flow: Source
  • TodoMVC: Source | Sandbox
  • Shopping Cart: Source | Sandbox
  • Tree View: Source | Sandbox
  • Async: Source | Sandbox
  • Universal: Source
  • Real World: Source | Sandbox

Если вы новичок в экосистеме NPM и имеете проблемы с получением и запуском проекта или не уверены, куда вставить шаблон, попробуйте simplest-redux-example который использует Redux вместе с React и Browserify.

Отзывы

“Love what you’re doing with Redux”
Jing Chen, автор Flux

“I asked for comments on Redux in FB’s internal JS discussion group, and it was universally praised. Really awesome work.”
Bill Fisher, автор документации Flux

“It’s cool that you are inventing a better Flux by not doing Flux at all.”
André Staltz, creator of Cycle

Благодарности

  • The Elm Architecture за великолепное введение в моделирование обновления состояния посредством редьюсеров;
  • Turning the database inside-out за взрыв моего сознания;
  • Developing ClojureScript with Figwheel за убеждение меня в том, что переоценка должна «просто работать»;
  • Webpack за Hot Module Replacement;
  • Flummox за обучение меня подходу Flux без шаблонов или синглетонов;
  • disto за доказательство концепции «hot reloadable» сторов;
  • NuclearJS за доказательство того, что такая архитектура может быть производительной;
  • Om за популяризацию идеи одного атома состояния;
  • Cycle за демонстрацию того, как часто функция является лучшим инструментом;
  • React за прагматические инновации.

Особенная благодарность Jamie Paton за передачу прав на redux NPM пакет.

Логотип

Вы можете найти официальное лого на GitHub.

История изменений

Это проект придерживается принципов семантического версионирования.
Каждый релиз, вместе с инструкциями миграции, документированы на странице релизов Github.

Меценаты

Разработка Redux была профинансирована сообществом.
Познакомьтесь с некоторыми из выдающихся компаний, которые сделали это возможным:

  • Webflow
  • Ximedes

См. полный список меценатов Redux., а также постоянно растущий список людей и компаний, которые используют Redux.

License

MIT

Время на прочтение
11 мин

Количество просмотров 230K

image

Оглавление

Введение
1. Установка и начало работы
2. Redux
….2.1 createStore
….2.2 reducer()
….2.3 dispatch()
….2.4 actionCreator()
….2.5 Actions
….2.6 getState()
….2.7 subscribe()
….2.8 combineReducers()
….2.9 initialState
3. React-redux
….3.1 Provider
….3.2 mapStateToProps()
….3.3 mapDispatchToProps()
….3.4 connect()

Введение

Вот вы прочитали мою статью про React (если нет, то настоятельно рекомендую вам сделать это) и начали разрабатывать приложения на нём. Но что это? Вы замечаете, как с расширением вашего приложения становится всё сложнее следить за текущим состоянием, сложно следить за тем, когда и какие компоненты рендарятся, когда они не рендарятся и почему они не рендарятся, сложно следить за потоком изменяющихся данных. Для этого и есть библиотека Redux. Сам React хоть и лёгкий, но для комфортной разработки на нем нужно много чего изучить.

И сегодня мы разберём 2 библиотеки: Redux и React-redux. Для использования Redux’а вам не нужно скачивать дополнительных библиотек, но, если использовать его в связке с библиотекой React-redux разработка становится ещё удобнее и проще.

Все примеры из этой статьи вы можете найти в этом репозитории на Github. Там находится полностью настроенное приложение React с использованием Redux и React-redux. Вы можете использовать его как начальную точку для вашего проекта. Изменяйте названия файлов и добавляйте новые в этот репозитории для создания собственного приложения. Смотрите во вкладку релизы для того что бы найти разные версии приложения. Первая содержит приложение только с использованием Redux, второе с использованием Redux и React-redux.

Мотивация использования Redux

Механизм локального хранилища компонента, который поставляется вместе с базовой библиотекой (React) неудобен тем, что такое хранилище изолировано. К примеру, если вы хотите, чтобы разные независимые компоненты реагировали на какое-либо событие, вам придётся либо передавать локальное состояние в виде пропсов дочерним компонентам, либо поднимать его вверх до ближайшего родительского компонента. В обоих случаях делать это не удобно. Код становится более грязным, трудночитаемым, а компоненты зависимыми от их вложенности. Redux снимает эту проблему так как всё состояние доступно всем компонентом без особых трудностей.

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

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

Используете ли вы Yarn или Npm, выполните одну из этих команд для установки Redux:

# NPM
npm install redux

# Yarn
yarn add redux 

Скорее всего вы используете папку src в которой хранится ваша кодовая база. Файлы, связанные с redux принято хранить в отдельной папке. Для этого я использую папку /src/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

Конечно здесь я использовал примитивные названия для файлов, это сделано для наглядности. В настоящем проекте так называть файлы не стоит.

2. Redux

2.1 createStore

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

Глобальное хранилище приложения создаётся в отдельном файле, который как правило называется store.js:

// Код файла store.js
import { createStore } from 'redux';

const store = createStore(reducer);

export default store;

2.2 reducer()

reducer — чистая функция которая будет отвечать за обновление состояния. Здесь реализовывается логика в соответствие с которой будет происходить обновление полей store.

Так выглядит базовая функция reducer:

function reducer(state, action) {
    switch(action.type) {
        case ACTION_1: return { value: action.value_1 };
        case ACTION_2: return { value: action.value_2 };
        
        default: return state;
    }
}

Функция принимает значение текущего состояния и обьект события (action). Обьект события содержит два свойства — это тип события (action.type) и значение события (action.value).

К примеру если нужно обработать событие onChange для поля ввода то объект события может выглядеть так:

{
    type: "ACTION_1",
    value: "Здесь значение поля формы"
}

Некоторые события могут не нуждаться в передаче каких-либо значении. К примеру, обрабатывая событие onClick мы можем сигнализировать о том, что событие произошло, более никаких данных не требуется, а как на него реагировать будет описывать логика, заложенная непосредственно в сам компонент которой должен на него реагировать и частично в reducer. Но во всех случаях необходимо определять тип события. Редьюсер как бы спрашивает: что произошло? actio.type равен «ACTION_1» ага значит произошло событие номер 1. Дальше его нужно как то обработать и обновить состояние. То, что вернёт редьюсер и будет новым состоянием.

ACTION_1 и ACTION_2 это константы событий. По-другому Actions. Про них мы поговорим далее 2.5 Actions.

Как вы уже догадались store может хранить сложную структуру данных состоящих из набора независимых свойств. Обновление одного свойства оставит нетронутым другие свойства. Так из примера выше, когда происходит событие номер один (ACTION_1) обновляется поле номер один (value_1) в store при этом поле номер два (value_2) остаётся нетронутым. В общем механизм схож с методом this.setState().

2.3 dispatch()

Что бы обновить store необходимо вызвать метод dispatch(). Он вызывается у объекта store который вы создаёте в store.js. Этот объект принято называть store поэтому обновление состояния в моём случае выглядит так:

store.dispatch({ type: ACTION_1, value_1: "Some text" });

ACTION_1 это константа события о которой речь пойдет дальше (см. Actions).

Эта функция вызовет функцию reducer который обработает событие и обновит соответствующие поля хранилища.

2.4 actionCreator()

На самом деле передавать объект события напрямую в dispatch() является признаком плохого тона. Для этого нужно использовать функцию под названием actionCreator. Она делает ровно то что и ожидается. Создаёт событие! Вызов этой функции нужно передавать как аргумент в dispatch а в actionCreator передавать необходимое значение (value). Базовый actionCreator выглядит следующим образом:

function action_1(value) {
    return { 
        type: ACTION_1,
        value_1: value
    };
}

export default action_1;

Таким образом вызов dispatch должен выглядеть так:

store.dispatch(action_1("Some value"));

С использованием actionCreator код становится более чистым.

2.5 Actions

actions это константы, описывающие событие. Обычно это просто строка с названием описывающее событие. К примеру константа описывающее событие номер один будет выглядеть следующем образом:

const ACTION_1 = "ACTION_1";

export default ACTION_1;

Опять же в проекте вам стоит называть константы в соответствии с событием, которое она описывает: onClick, createUserSesion, deleteItem, addItem и т.д. Главное, чтобы было понятно. Замете что я нигде не писал import поэтому не забудьте импортировать ваши константы перед их использованием. Потому что константы тоже принято разбивать на отдельные файлы храня их в специальной папке. Хотя некоторые хранят их в одном файле под названием actionTypes.js. Такое решение нельзя назвать не правильным, но и не идеальным.

2.6 getState()

С помощью dispatch() обновили, а как теперь посмотреть новое значение store? Ничего изобретать не нужно, есть метод getState(). Он также, как и метод dispatch вызывается на экземпляре объекта store. Поэтому для моего примера вызов

store.getState()

вернёт значение полей хранилища. К примеру что бы посмотреть значение поля value_1 необходимо будет вызвать

store.getState().value_1

2.7 subscribe()

А как же узнать, когда состояние обновилось? Для этого есть метод subscribe(). Он также вызывается на экземпляре store. Данный метод принимает функцию, которая будет вызывается каждый раз после обновления store. Он как бы «подписывает» функцию, переданную ему на обновление. К примеру следующий код при каждом обновлении (при каждом вызове dispatch()) будет выводить новое значение store в консоль.

store.subscribe(() => console.info(store.getState()))

Этот метод возвращает функцию unsubscribe(). Которая позволяет «отписаться от обновления». К примеру если компонент удаляется из DOM стоит отписать его методы от обновления в componentWillUnmount(). Этот метод жизненного цикла вызывается при размонтировании компонента и это именно то место где стоит отписываться от обновления. Проще говоря в деструкторе.

2.8 combineReducers()

combineReducers() позволяет объединить несколько редьюсеров в один.

Если логика обновления компонентов довольно сложна иили необходимо обрабатывать большое количество различных типов событий, то корневой reducer может стать слишком громоздким. Лучшим решением будет разбить его на несколько отдельных редьюсеров каждый из которых отвечает за обработку только одного типа событий и обновления определённого поля.

Внимание!

Когда вы разбиваете базовый редьюсер на несколько, то название каждого из них должно соответствовать полю которое он обновляет в store.

К примеру если редьюсер обновляет поле номер один, то он может выглядеть так:

function value_1(state, action) {
    switch(action.type) {
        case ACTION_1: return action.value_1;
        
        default: return state;
    }
}

export default value_1;

Название редьюсера (value_1) показывает какое свойство он будет обновлять в store. Если переименуете его в value_2 то он станет обновлять value_2. Поэтому учтите это!

Когда используется единый редьюсер мы показываем какое поле хотим обновить:

 case ACTION_1: return { value_1: action.value_1 };

Но когда вы разделили ваши редьюсеры вам нужно просто вернуть новое значение:

case ACTION_1: return action.value_1;

Поскольку здесь не требуется указывать которое из полей обновляет редьюсер ибо его название и есть поле которое он обновляет.

2.9 initialState

initialState — объект, представляющий начальное состояние хранилища. Он является вторым не обязательным аргументом метода createStore(). С созданием хранилища можно сразу объявить начальное состояние для его полей. Этот объект желательно создавать, даже в тех случаях, когда объявления начального состояния не требуется. Потому что этот объект помогает посмотреть на структуру хранилища и название его полей. Обычный объект initialState выглядит следующим образом:

const initialState = {
    date_1: "value...",
    date_2: "value..."
};

export default initialState;

В некоторых случаях (когда компонент сразу использует значение из store), его объявление может стать обязательным иначе вы получите ошибку: TypeError: Cannot read property ‘value_1’ of undefined.

Также редьюсеры всегда должны возвращать по дефолту текущее состояние. К примеру, если используется единый reducer то последнее значение в switch должно выглядеть так:

default: return store;

Если же вы разделяете редьюсеры на независимые функции, то он должен возвращать значение того свойства за которое он отвечает:

default: return store.value_1;

Также если вы не передаёте объект initialState в createStore вы можете вернуть его из редьюсера. В обоих случаях будет инициализировано начальное состояние для store.

3. React-redux

Казалось бы, у нас есть всё что бы использовать Redux. Но на деле использование его без пакета React-redux в React приложениях выглядит не очень красиво.

3.1 Provider

Для использование store в компоненте вам необходимо передавать его в пропсы:

ReactDOM.render(<Main store={store} />, document.getElementById('root'));

И после использовать в компоненте: this.props.state. Для этого react-redux предостовляет метод Provider:

ReactDOM.render(
    <Provider store={store}>
        <Main />
    </Provider>, 
document.getElementById('root'));

Таким образом метод connect сможет использовать store. В противном случае вы получите ошибку: Error: Could not find «store» in the context of «Connect(Main)». Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(Main) in connect options.

Также можно передать store напрямую в компонент, не оборачивая его в Provider и это будет работать. Но лучше всё-таки используйте Provider.

3.2 mapStateToProps()

Этот метод вызывается всякий раз, когда происходит обновление store и именно он передаёт необходимые свойства из store в компонент. К примеру компонент, должен реагировать и обновлять UI каждый раз, когда поле номер один (value_1) обновилось. На обновление других полей ему реагировать не нужно. Если вы не используете React-redux вам бы пришлось использовать метод subscribe() что бы узнавать об обновлении и далее каким то образом проверять обновилось ли поле номер один или нет. В общем несложно понять, что такой код будет выглядеть слишком грязным и избыточным. С помощью mapStateToProps() можно чётко определить какие поля интересуют компонент. И на какие поля он должен реагировать.

Возвращаясь к примеру выше, если компоненту один нужно получать поле номер один (value_1) то mapStateToProps для него будет выглядеть следующим образом:

function (state) {
    return {
        value_1: state.value_1
    };
}

После внутри компонента мы можем обращается к полю value_1 через this.props.value_1. И каждый раз когда это поле будет обновляется компонент будет рендерится заново.

Вы можете создать отдельную папку в /src/store для хранения файлов каждый из которых будет содержать функцию mapStateToProps для всех ваших компонентов. Либо (как сделал это я) использовать единую функцию возвращающую функцию mapStateToProps для каждого компонента. Лично мне нравится такой подход. Такая функция выглядит следующим образом:

function mapStateToProps(component) {
    switch(component) {
        case "Component_1": {
            return function (state) {
                return {
                    value_1: state.value_1
                };
            }
        }
        case "Component_2": {
            return function(state) {
                return {
                    value_2: state.value_2
                };
            }
        }
        default: return undefined;
    }
}

export default mapStateToProps;

Эта функция в качестве аргумента принимает строку с названием компонента и возвращает функцию mapStateToProps которая возвращает объект со свойством из store необходимом для данного компонента. Эту функцию можно назвать mapStateToPropsGenerator().

3.3 mapDispatchToProps()

Эта функция передаёт в компонент методы для обновления необходимого поля store. Что бы не вызывать dispatch напрямую из компонента вы будете использовать данный метод для того что бы передавать в props метод вызов которого приведёт к вызову dispatch и обновлению соответствующего поля. Просто теперь это будет выглядеть более элегантно, а код более понятным и чистым.

К примеру компонент, номер один должен иметь возможность обновлять поле номер один из store. Тогда mapDispatchToProps для него будет выглядеть следующим образом:

function (dispatch) {
    return {
        changeValue_1: bindActionCreators(action_1, dispatch)
    };
};

Теперь для обновления свойства value_1 вы будете вызывать changeValue_1() через this.props.changeValue_1(value). Не вызывая dispatch напрямую через this.props.store.dispatch(action_1(value)).

bindActionCreators следует импортировать из redux. Он позволяет оборачивать функцию dispatch и actionCreator в единый объект. Вы можете не использовать bindActionCreators но тогда код будет выглядеть избыточным. Вы должны старятся реализовать какую-либо функциональность так, чтобы код выгладил просто и миниатюрно. Поэтому ничего лишнего писать не следует.

Только чистый и понятный код. Метод bindActionCreators(actionCreator, dispatch) принимает два обязательных параметра это функцию actionCreator о которой мы уже говорили и dispatch. Возвращая метод для изменения полей store.

Также как и для mapStateToProps я использую функцию генератор возвращающую функцию mapDispatchToProps для каждого компонента:

import { bindActionCreators } from 'redux';
import action_1 from './actionCreators/action_1';
import action_2 from './actionCreators/action_2';

function mapDispatchToProps(component) { 
    switch(component) {
        case "Component_1": return function(dispatch) {
            return {
                change_value_1: bindActionCreators(action_1, dispatch)
            };
        };
        case "Component_2": return function(dispatch) {
            return {
                change_value_2: bindActionCreators(action_2, dispatch)
            };
        };
        default: return undefined;
    }
}

export default mapDispatchToProps;

3.4 connect()

Ну и теперь кульминация! То без чего всё это не будет работать. Это функция connect.
Именно она связывает mapStateToProps и mapDispatchToProps с компонентом и передает необходимые поля и методы в него. Возвращает она новый компонент-обёртку для вашего компонента. Как правильно именовать такой компонент я не знаю, ибо в самой документации React-redux это не описывается. Лично я добавляю окончание _w для компонентов оберток. Как бы _w = wrap Component. Подключение компонента в этм случае выглядит так:

const COMPONENT_1_W = connect(mapStateToProps("Component_1"), mapDispatchToProps("Component_1"))(Component_1);

И теперь в ReactDOM.render() вы передаёте не ваш компонент, а тот что возвращает функция connect.

Если же у компонента нет необходимости в передаче ему mapStateToProps или mapDispatchToProps передавайте undefined или null в него.

Что такое Redux ? Это менеджер состояний. Чаще всего его используют с React, но его возможности не ограничиваются одной этой библиотекой. Хотя в React есть собственный метод управления состояниями (почитать о нём можно в руководстве по React), он плохо масштабируется. Перемещение состояния вверх по дереву работает для простых приложений, но в более сложных архитектурах изменение состояния производится через свойства (props). Ещё лучше делать это через внешнее глобальное хранилище.

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

Примечание Вы читаете улучшенную версию некогда выпущенной нами статьи.
Содержание:

  • Когда нужно пользоваться Redux?
  • Использование Redux
        • Неизменяемое дерево состояний
        • Действия
        • Типы действий должны быть константами
        • Генераторы действий
        • Редукторы
  • Хранилище
  • Поток данных

Redux идеально использовать в средних и крупных приложениях. Им стоит пользоваться только в случаях, когда невозможно управлять состоянием приложения с помощью стандартного менеджера состояний в React или любой другой библиотеке.

Простым приложениям Redux не нужен.

Использование Redux

Разберём основные концепции библиотеки Redux, которые нужно понимать начинающим.

Неизменяемое дерево состояний

В Redux общее состояние приложения представлено одним объектом JavaScript — state (состояние) или state tree (дерево состояний). Неизменяемое дерево состояний доступно только для чтения, изменить ничего напрямую нельзя. Изменения возможны только при отправке action (действия).

Действия

Действие (action) — это JavaScript-объект, который лаконично описывает суть изменения:

{ 
  type: 'CLICKED_SIDEBAR' 
} 
// подробности об изменении 
{ 
  type: 'SELECTED_USER', 
  userId: 232 
}

Единственное требование к объекту действия — это наличие свойства type, значением которого обычно является строка.

Типы действий должны быть константами

В простом приложении тип действия задаётся строкой. По мере разрастания функциональности приложения лучше переходить на константы:

const ADD_ITEM = 'ADD_ITEM' 
const action = { type: ADD_ITEM, title: 'Third item' }

и выносить действия в отдельные файлы. А затем их импортировать:

import { ADD_ITEM, REMOVE_ITEM } from './actions'

Генераторы действий

Генераторы действий (actions creators) — это функции, создающие действия.

function addItem(t) { 
  return { 
    type: ADD_ITEM, 
    title: t 
  } 
}

Обычно инициируются вместе с функцией отправки действия:

dispatch(addItem('Milk'))

Или при определении этой функции:

const dispatchAddItem = i => dispatch(addItem(i))
dispatchAddItem('Milk')

Редукторы

При запуске действия обязательно что-то происходит и состояние приложения изменяется. Это работа редукторов.

Что такое редуктор

Редуктор (reducer) — это чистая функция, которая вычисляет следующее состояние дерева на основании его предыдущего состояния и применяемого действия.

(currentState, action) => newState

Чистая функция работает независимо от состояния программы и выдаёт выходное значение, принимая входное и не меняя ничего в нём и в остальной программе. Получается, что редуктор возвращает совершенно новый объект дерева состояний, которым заменяется предыдущий.

Чего не должен делать редуктор

Редуктор — это всегда чистая функция, поэтому он не должен:

  • мутировать аргументы;
  • мутировать состояние. Вместо этого создаётся новое состояние с помощью Object.assign({}, ...);
  • иметь побочные эффекты (никаких API-вызовов с какими-либо изменениями);
  • вызывать нечистые функции. Это функции, результат которых зависит от чего-то кроме их аргументов (например, Date.now() или Math.random()).

Поскольку состояние в сложных приложениях может сильно разрастаться, к каждому действию применяется не один, а сразу несколько редукторов.

Симулятор редуктора

Упрощённо базовую структуру Redux можно представить так:

Состояние
{ 
  list: [ 
    { title: "First item" }, 
    { title: "Second item" }, 
  ], 
  title: 'Grocieries list' 
}
Список действий
{ type: 'ADD_ITEM', title: 'Third item' } 
{ type: 'REMOVE_ITEM', index: 1 } 
{ type: 'CHANGE_LIST_TITLE', title: 'Road trip list' }
Редуктор для каждой части состояния
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_ITEM': 
      return state.concat([{ title: action.title }]) 
    case 'REMOVE_ITEM': 
      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), 
  } 
}

Хранилище

Хранилище (store) — это объект, который:

  • содержит состояние приложения;
  • отображает состояние через getState();
  • может обновлять состояние через dispatch();
  • позволяет регистрироваться (или удаляться) в качестве слушателя изменения состояния через subscribe().

Хранилище в приложении всегда уникально. Так создаётся хранилище для приложения listManager:

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()

Поток данных

Поток данных в Redux всегда однонаправлен.

Передача действий с потоками данных происходит через вызов метода dispatch() в хранилище. Само хранилище передаёт действия редуктору и генерирует следующее состояние, а затем обновляет состояние и уведомляет об этом всех слушателей.

***

Советуем начинающим в Redux прочитать нашу статью о других способах передачи данных.

Перевод статьи «A quick guide to Redux for beginners»

Ольга Сайфудинова

Это руководство научит вас основам того, как работает Redux-приложение , шаг за шагом. В этом руководстве мы рассмотрим процесс создания простого приложения Todo.

  • Экшены (Actions)
  • Редьюсеры (Reducers)
  • Стор (Store)
  • Поток данных (Data Flow)
  • Использвание с React
  • Пример: Todo List

Знаете JS и React? Прочитав наше руководство, вы освоите Redux. В этом материале разбираемся, как написать Redux-приложение.

Для примера возьмём Redux-приложение «Hello World», и будем с ним работать далее.

Как устроено Redux-приложение «Hello World»

Всё построено на Bootstrap с использованием функции create-react-app, так что вы, скорее всего, знаете, как это работает. Ещё, нам пригодится вот этот репозиторий GitHub.

Итак, у нас есть файл index.js, который отображает <App /> в DOM. Этот App состоит из <HelloWorld /> и с помощью tech выполняет то, что хочет пользователь.

Если задать <HelloWorld tech=»React» />, то на выходе вы получите это:

Основное приложение Hello World с состоянием по умолчанию «React»

А если <HelloWorld tech=»Redux» />, то это:

Redux-приложение

Посмотрите, как выглядит компонент App:

src/App.js

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";

class App extends Component {
 state = { 
  tech : "React"
}
render() {
  return <HelloWorld tech={this.state.tech}/>
}
}

export default App;

Взгляните на объект state, в котором находится tech. Он переходит вниз также, как prop в компонент HelloWorld:

<HelloWorld tech={this.state.tech}/>

Пока не стоит задумываться над тем, как реализуется компонент HelloWorld. Он просто использует базу tech и добавляет всякие штуки из CSS.

Но не будем останавливаться на стилях. Сейчас вопрос в другом: как провести рефакторинг, чтобы всё нормально работало в Redux? Об этом ниже.

Что гласит документация? То, что Redux − предсказуемый контейнер состояния. Таковым его делает state, с его помощью приложением можно управлять. Вот так выглядит состояние для компонента <App/>:

{
 tech: "React"
}

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

И первое, что нужно сделать − удалить состояние компонента внутри <App />:

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";

class App extends Component {
 // the state object has been removed. 
render() {
  return <HelloWorld tech={this.state.tech}/>
}
}

export default App;

Это ещё не всё, но <App/> уже не имеет состояния.

Установите Redux, запустив yarn install redux с помощью интерфейса командной строки (CLI).

Создаём Redux Store

Так как <App /> больше не может управлять своим состоянием, нужно настроить Redux Store для управления состоянием нашего приложения.

В банке это работает так: нанимаются несколько инженеров, программистов, специалистов по безопасности, юристов, которые десятки раз согласовывают процессы между собой. У нас всё проще: для создания управляемого хранилища нужны лишь навыки программирования, а остальное сделают API в Redux.

Так выглядит код store, благодаря которому мы осуществим задуманное:

import { createStore } from "redux"; // импорт из Redux-библиотеки
const store = createStore();  // пока невыполнимое действие

Для начала импортируем функцию createStore из Redux. Затем вызываем функцию createStore(), чтобы создать хранилище.

Теперь createStore принимает несколько значений. Первое − reducer.

Отношения между хранилищем и редуктором

Руководство по Redux: пишем первое Redux-приложение

И снова вернемся к банку. При снятии денег вы сообщаете кассиру о намерении получить их с помощью функции WITHDRAW_MONEY. И не получаете их сразу же. Потому что сначала кассир должен проверить, есть ли деньги у него и есть ли они на вашем счету. Можно считать, что кассир неотделим от хранилища, ведь если бы они не работали в связке друг с другом, денег бы вы не получили.

Redux store − это хранилище, а Redux reducer − это кассир, и они всегда должны быть синхронизированы.

Руководство по Redux: пишем первое Redux-приложение

Почему так? Редуктор всегда обращается к хранилищу. И хранилище всегда ждёт запроса от редуктора. Это единственный, кто может послать запрос createStore().

Редуктор является обязательным аргументом, переданным в "createStore"

Далее, мы рассмотрим, как работает reducer, и попробуем связать его со store с помощью createStore.

Reducer

Официальная документация гласит: редукторы − самые важные концепты в Redux.

Иногда их называют сокращающими функциями. Возможно, вы пользовались ими, например − Array.reduce().

Посмотрим, как это работает на отрывке кода:

let arr = [1,2,3,4,5]
let sum = arr.reduce((x,y) => x + y)
console.log(sum)  //15

Это самый простой способ найти сумму чисел в массиве в JavaScript. Здесь редуктор принимает два значения − accumulator и currentValue, где x − accumulator, а y − currentValue.

Redux Reducer − это просто функция, которая принимает два параметра. Первый − state, а другой − action.

createStore() отвечает за передачу аргументов редуктору. Немного опустим подробности работы редуктора. Но минимальный код для создания хранилища знать нужно:

import { createStore } from "redux";  
const store = createStore(reducer);

Процесс рефакторинга

Если вы запутались в предыдущем топике, перечитайте его ещё раз или задайте вопрос автору гайда.

Посмотрите на код ниже:

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";

 import { createStore } from "redux";  
 const store = createStore(reducer);  

 class App extends Component {
 render() {
   return <HelloWorld tech={this.state.tech}/>
 }
}

export default App;

Заметили ошибку? В четвертой строке редуктор обращается к функции, которой ещё не существует. Исправим это (редуктор − всего лишь функция) путём создания директории редукторов и файла index.js внутри неё.

Для начала экспортируйте функцию в этот файл:

export default () => {
}

Редуктор всегда что-то возвращает. В примере с Array.reduce() мы вернули сумму и текущее значение.

Для редукторов вы всегда возвращаете newState вашего приложения.

Объясним на примере.

После того как вы придёте в банк и снимите деньги, денег на счету у вас станет меньше и кассир об этом знает.

Поскольку нас пока не волнует обновление состояния, мы будем продолжать возвращаться в newState по мере прохождения state состояния.

Вот так это работает в редукторе:

export default (state) => {
                   return state        
}

Если действие в банке не производится, то ничего не происходит. Так и у нас: если никакой action не был отправлен, то приложение будет всегда возвращаться в позицию state.

Второй аргумент createStore

Руководство по Redux: пишем первое Redux-приложение

Когда вы приходите в банк, чтобы снять деньги, вы понимаете, что они там есть, либо когда-то были. Назовём эти деньги начальным депозитом. В Redux тоже есть возможность создания начального депозита с помощью функции initialState:

const store = createStore(reducer, initialState);

После совершения нескольких банковских операций, депозит станет обычным счётом. На выходе мы получим вот такой код:

App.js

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";
import reducer from "./reducers";
import { createStore } from "redux";  

const initialState = { tech: "React " };
const store = createStore(reducer, initialState);

class App extends Component {
 render() {
   return <HelloWorld tech={this.state.tech}/>
 }
 }

export default App;

reducers/index.js

export default state  => {
                   return state        
}

Если вы сейчас попробуете запустить этот код, получите сообщение об ошибке. Так происходит потому, что <App /> больше не связан с объектом состояния. Поскольку теперь store управляет состоянием, state объект нужно убрать из store. Как?

Каждый раз, когда вы создаете хранилище createStore(), созданный store начинает работать с тремя методами. Первый − getState().

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

App.js

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";
import { createStore } from "redux";  

const initialState = { tech: "React " };
const store = createStore(reducer, initialState);  

class App extends Component {
 render() {
   return <HelloWorld tech={store.getState().tech}/>
 }
 }

Замените "this.state" на "store.getState ()"

Reducers/Reducer.js

export default state => {
                   return state        
}

И это все! Вы только что изучили основы Redux и успешно адаптировали простое приложение React для использования Redux.

Приложение React теперь имеет свое состояние, управляемое Redux. Все, что нужно получить от state объекта, будет взято из store.

Надеемся, вы поняли весь процесс рефакторинга. Если что, можете посмотреть материалы Github diffп.

С помощью проекта «Hello World» мы рассмотрели основные концепции и написали Redux-приложение. Несмотря на то, что это крошечный проект, он обеспечивает достойную основу для понимания и развития.

Вот несколько вещей, которые вы должны усвоить:

  • Redux является предсказуемым контейнером состояний для приложений JavaScript.
  • createStore используется для создания Redux store.
  • Редуктор − единственный, кто имеет доступ к функции createStore().
  • Редуктор − это просто функция. Функция, которая принимает два параметра. Первое − state, второе − action.
  • Редуктор всегда возвращает newState.
  • Исходное состояние вашего приложения − initialState − это второй аргумент, который передается в createStore.
  • getState() возвращает текущее состояние вашего приложения.

Интересно было узнать о том, как написать первое Redux-приложение?

Вас также могут заинтересовать другие статьи по теме:

  • Знакомимся с Redux: основные принципы JavaScript-библиотеки
  • Пора понять замыкания в JavaScript! Часть 1. Готовим фундамент
  • Пора понять замыкания в JavaScript! Часть 2. Переходим к делу

Базовое руководство

Добро пожаловать в Redux Toolkit! Это руководство покажет вам основные функции, которые включены в Redux Toolkit (также известный как «RTK», для краткости).

В этом руководстве предполагается, что вы уже знакомы с концепциями основной библиотеки

Redux

, а также с тем, как использовать ее с

React

. Если это не так, пожалуйста, сначала прочтите

документацию Redux

и

React-Redux

, поскольку объяснения здесь сфокусированы на том, чем использование RTK отличается от «типичного» кода Redux.

Мы начнем с рассмотрения самого маленького примера Redux: простого счетчика.

Пример Redux «Counter-Vanilla»

Документация Redux содержит

пример «counter-vanilla»

, который демонстрирует, как создать простой Redux стор с редьюсером, который хранит числовое значение и реагирует на типы действий «INCREMENT» и «DECREMENT». Вы можете увидеть

полный код в CodeSandbox

, но вот важный раздел:

function counter(state, action) {

if (typeof state === ‘undefined’) {

var store = Redux.createStore(counter)

document.getElementById(‘increment’).addEventListener(‘click’, function() {

store.dispatch({ type: ‘INCREMENT’ })

В этом разделе создается функция-редьюсер, называемая counter, проверяется, что она использует для состояния по умолчанию 0, прослушиваются типы экшенов "INCREMENT"и "DECREMENT" и диспатчится экшен "INCREMENT", когда кнопка нажата.

Более типичный пример счетчика

Хотя этот пример прост, он также и несколько нереалистичен. Большинство приложений Redux написано с использованием синтаксиса ES6, включая аргументы по умолчанию для параметров функции, которые не определены. Также распространена практика написания

функций для создания экшенов

, вместо использования объектов непосредственно в коде и использования констант для типов экшенов, вместо написания простых строк каждый раз.

Давайте перепишем приведенный выше пример, используя эти подходы, чтобы увидеть, как он будет выглядеть:

const INCREMENT = ‘INCREMENT’

const DECREMENT = ‘DECREMENT’

return { type: INCREMENT }

return { type: DECREMENT }

function counter(state = 0, action) {

const store = Redux.createStore(counter)

document.getElementById(‘increment’).addEventListener(‘click’, () => {

store.dispatch(increment())

Поскольку пример небольшой, это не сильно влияет на внешний вид. Что касается размера, мы сократили пару строк, используя аргумент по умолчанию, но добавление этих генераторов экшенов увеличило размер кода. И здесь есть некоторое дублирование. Написание const INCREMENT = 'INCREMENT‘ выглядит довольно глупым :) Особенно, когда оно используется только в двух местах — в генераторе экшена и редьюсере.

Кроме того, многих людей беспокоят операторы switch. Было бы неплохо, если бы мы могли заменить его какой-нибудь таблицей поиска.

Представляем: configureStore

Redux Toolkit включает несколько функций, которые помогают упростить ваш код Redux. Первая функция, которую мы рассмотрим, это

configureStore

.

Обычно вы создаете Redux стор, вызывая createStore() и передавая вашу корневую функцию-редьюсер. Redux Toolkit имеет функцию configureStore(), которая обертывает createStore(), чтобы делать то же самое, но также настраивает для вас некоторые полезные инструменты разработки как часть процесса создания стора.

Мы можем легко заменить существующий вызов createStore на configureStore. configureStore принимает один объект с именованными полями, вместо нескольких аргументов функции, поэтому нам нужно передать нашу функцию-редьюсер, как поле с именемreducer:

const store = createStore(counter)

const store = configureStore({

Представляем: createAction

createAction принимает тип экшена в качестве аргумента и возвращает генератор экшена, который использует этот строковый тип. (Да, согласны, имя немного неверно — мы создаем «функцию генератора экшена», а не «объект экшена», но оно короче и легче запоминается, чем createActionCreator.) Итак, эти два примера эквивалентны :

// Оригинальный подход: написать тип экшена и генератор экшена вручную

const INCREMENT = ‘INCREMENT’

function incrementOriginal() {

return { type: INCREMENT }

console.log(incrementOriginal())

// Или, используя `createAction`

const incrementNew = createAction(‘INCREMENT’)

console.log(incrementNew())

Но что, если нам нужно ссылаться на строку типа экшена в редьюсере? С createAction это можно сделать двумя способами. Во-первых, метод генератора экшена toString () был переопределен и возвращает строку типа экшена. Во-вторых, строка типа также доступна, как поле .type функции:

const increment = createAction(‘INCREMENT’)

console.log(increment.toString())

console.log(increment.type)

Мы можем использовать createAction, чтобы упростить предыдущий пример счетчика.

const increment = createAction(‘INCREMENT’)

const decrement = createAction(‘DECREMENT’)

function counter(state = 0, action) {

const store = configureStore({

document.getElementById(‘increment’).addEventListener(‘click’, () => {

store.dispatch(increment())

Это снова сэкономило нам несколько строк, и, по крайней мере, мы не повторяем слово INCREMENT везде.

Представляем: createReducer

Теперь давайте посмотрим на функцию редьюсера. Хотя вы можете использовать любую условную логику, какую захотите, в редьюсере Redux, включая операторы if и циклы, наиболее распространенным подходом, является проверка поляaction.type и выполнение определенной логики для каждого типа экшена. редьюсер также предоставит значение начального состояния и вернет существующее состояние, если экшен его не интересует

Redux Toolkit предоставляет

функциюcreateReducer

, которая позволяет вам писать редьюсеры, используя «таблицу поиска», где каждый ключ в объекте представляет собой строку типа экшена Redux, а значения функции редьюсера. Мы можем использовать ее для прямой замены существующего определения функции counter. Поскольку нам нужно использовать строки типа экшена в качестве ключей, мы можем использовать

синтаксис «вычисляемого свойства» объекта ES6

для создания ключей из строковых переменных типа.

const increment = createAction(‘INCREMENT’)

const decrement = createAction(‘DECREMENT’)

const counter = createReducer(0, {

[increment.type]: state => state + 1,

[decrement.type]: state => state 1

Или, поскольку синтаксис вычисленных свойств будет вызывать toString() для любой переменной внутри, мы можем просто использовать функции генератора экшена напрямую без поля .type:

const counter = createReducer(0, {

[increment]: state => state + 1,

[decrement]: state => state 1

Представляем: createSlice

Давайте рассмотрим, как выглядит пример счетчика на этом этапе:

const increment = createAction(‘INCREMENT’)

const decrement = createAction(‘DECREMENT’)

const counter = createReducer(0, {

[increment]: state => state + 1,

[decrement]: state => state 1

const store = configureStore({

document.getElementById(‘increment’).addEventListener(‘click’, () => {

store.dispatch(increment())

Это неплохо, но мы можем внести в это еще одно серьезное изменение. Зачем нам вообще нужно писать генераторы экшенов отдельно или записывать эти типов экшенов? Действительно важная часть здесь — это функции редьюсера.

Именно здесь на помощь приходит

функция createSlice

. Она позволяет нам предоставить объект c функциями редьюсера и автоматически генерирует строки типов экшенов и функции генераторов экшенов на основе названия перечисленных нами редьюсеров.

createSlice возвращает объект «slice», который содержит сгенерированную функцию редьюсерв в виде поля с именем «reducer» и сгенерированные генераторы экшенов, внутри объекта с именем «actions».

Вот как будет выглядеть наш пример счетчика, с использованием вместо этого createSlice:

const counterSlice = createSlice({

increment: state => state + 1,

decrement: state => state 1

const store = configureStore({

reducer: counterSlice.reducer

document.getElementById(‘increment’).addEventListener(‘click’, () => {

store.dispatch(counterSlice.actions.increment())

В большинстве случаев вы, вероятно, захотите использовать синтаксис деструктуризации ES6, чтобы вытащить функции генератора экшена в качестве переменных, а также, возможно, редьюсер:

const { actions, reducer } = counterSlice

const { increment, decrement } = actions

Напомним, что делают функции:

  • configureStore: создает экземпляр стора Redux, как исходныйcreateStore из Redux, но принимает именованный объект параметров и автоматически устанавливает расширение Redux DevTools.

  • createAction: принимает строку типа экшена и возвращает функцию генератора экшена, которая использует этот тип

  • createReducer: принимает значение начального состояния и таблицу соответствия типов экшенов и функций редьюсера и создает редьюсер, который обрабатывает все эти типы экшенов.

  • createSlice: принимает начальное состояние и таблицу поиска с именами и функциями редьюсера и автоматически генерирует функции генератора экшена, строки типа экшена и функцию редьюсера.

Обратите внимание, что ничего из этого не изменило того, как работает Redux. Мы все еще создаем стор Redux, отправляем объекты экшенов, описывающие «что произошло» и возвращаем обновленное состояние с помощью функции редьюсера. Также обратите внимание, что функции Redux Toolkit могут использоваться независимо от того, какой подход использовался для создания пользовательского интерфейса, поскольку они просто обрабатывают часть кода, содержащую «простой стор Redux». В нашем примере мы использовали стор с «vanilla JS» UI, но мы могли бы использовать этот же стор с React, Angular, Vue или любым другим уровнем пользовательского интерфейса.

Наконец, если вы внимательно посмотрите на пример, вы увидите, что есть одно место, где мы написали некоторую асинхронную логику — кнопка «increment async»:

document.getElementById(‘incrementAsync’).addEventListener(‘click’, function() {

store.dispatch(increment())

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

Вот полный пример в песочнице:

</iframe> Теперь, когда вы знаете основы каждой функции, следующий шаг — попытаться использовать их в более крупном примере, чтобы увидеть, как они работают. Мы рассмотрим это в

руководстве для среднего уровня

.

id title sidebar_label description

part-1-overview-concepts

Redux Essentials, Part 1: Redux Overview and Concepts

Redux Overview and Concepts

The official Essentials tutorial for Redux: learn how to use Redux, the right way

import { DetailedExplanation } from ‘../../components/DetailedExplanation’

:::tip What You’ll Learn

  • What Redux is and why you might want to use it
  • Key Redux terms and concepts
  • How data flows through a Redux app

:::

Introduction

Welcome to the Redux Essentials tutorial! This tutorial will introduce you to Redux and teach you how to use it the right way, using our latest recommended tools and best practices. By the time you finish, you should be able to start building your own Redux applications using the tools and patterns you’ve learned here.

In Part 1 of this tutorial, we’ll cover the key concepts and terms you need to know to use Redux, and in Part 2: Redux App Structure we’ll examine a basic React + Redux app to see how the pieces fit together.

Starting in Part 3: Basic Redux Data Flow, we’ll use that knowledge to build a small social media feed app with some real-world features, see how those pieces actually work in practice, and talk about some important patterns and guidelines for using Redux.

How to Read This Tutorial

This page will focus on showing you how to use Redux the right way, and explain just enough of the concepts so that you can understand how to build Redux apps correctly.

We’ve tried to keep these explanations beginner-friendly, but we do need to make some assumptions about what you know already:

:::important Prerequisites

  • Familiarity with HTML & CSS.
  • Familiarity with ES6 syntax and features
  • Knowledge of React terminology: JSX, State, Function Components, Props, and Hooks
  • Knowledge of asynchronous JavaScript and making AJAX requests

:::

If you’re not already comfortable with those topics, we encourage you to take some time to become comfortable with them first, and then come back to learn about Redux. We’ll be here when you’re ready!

You should make sure that you have the React and Redux DevTools extensions installed in your browser:

  • React DevTools Extension:
    • React DevTools Extension for Chrome
    • React DevTools Extension for Firefox
  • Redux DevTools Extension:
    • Redux DevTools Extension for Chrome
    • Redux DevTools Extension for Firefox

What is Redux?

It helps to understand what this «Redux» thing is in the first place. What does it do? What problems does it help me solve? Why would I want to use it?

Redux is a pattern and library for managing and updating application state, using events called «actions». It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion.

Why Should I Use Redux?

Redux helps you manage «global» state — state that is needed across many parts of your application.

The patterns and tools provided by Redux make it easier to understand when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur. Redux guides you towards writing code that is predictable and testable, which helps give you confidence that your application will work as expected.

When Should I Use Redux?

Redux helps you deal with shared state management, but like any tool, it has tradeoffs. There are more concepts to learn, and more code to write. It also adds some indirection to your code, and asks you to follow certain restrictions. It’s a trade-off between short term and long term productivity.

Redux is more useful when:

  • You have large amounts of application state that are needed in many places in the app
  • The app state is updated frequently over time
  • The logic to update that state may be complex
  • The app has a medium or large-sized codebase, and might be worked on by many people

Not all apps need Redux. Take some time to think about the kind of app you’re building, and decide what tools would be best to help solve the problems you’re working on.

:::info Want to Know More?

If you’re not sure whether Redux is a good choice for your app, these resources give some more guidance:

  • When (and when not) to reach for Redux
  • The Tao of Redux, Part 1 — Implementation and Intent
  • Redux FAQ: When should I use Redux?
  • You Might Not Need Redux

:::

Redux Libraries and Tools

Redux is a small standalone JS library. However, it is commonly used with several other packages:

React-Redux

Redux can integrate with any UI framework, and is most frequently used with React. React-Redux is our official package that lets your React components interact with a Redux store by reading pieces of state and dispatching actions to update the store.

Redux Toolkit

Redux Toolkit is our recommended approach for writing Redux logic. It contains packages and functions that we think are essential for building a Redux app. Redux Toolkit builds in our suggested best practices, simplifies most Redux tasks, prevents common mistakes, and makes it easier to write Redux applications.

Redux DevTools Extension

The Redux DevTools Extension shows a history of the changes to the state in your Redux store over time. This allows you to debug your applications effectively, including using powerful techniques like «time-travel debugging».

Redux Terms and Concepts

Before we dive into some actual code, let’s talk about some of the terms and concepts you’ll need to know to use Redux.

State Management

Let’s start by looking at a small React counter component. It tracks a number in component state, and increments the number when a button is clicked:

function Counter() {
  // State: a counter value
  const [counter, setCounter] = useState(0)

  // Action: code that causes an update to the state when something happens
  const increment = () => {
    setCounter(prevCounter => prevCounter + 1)
  }

  // View: the UI definition
  return (
    <div>
      Value: {counter} <button onClick={increment}>Increment</button>
    </div>
  )
}

It is a self-contained app with the following parts:

  • The state, the source of truth that drives our app;
  • The view, a declarative description of the UI based on the current state
  • The actions, the events that occur in the app based on user input, and trigger updates in the state

This is a small example of «one-way data flow»:

  • State describes the condition of the app at a specific point in time
  • The UI is rendered based on that state
  • When something happens (such as a user clicking a button), the state is updated based on what occurred
  • The UI re-renders based on the new state

One-way data flow

However, the simplicity can break down when we have multiple components that need to share and use the same state, especially if those components are located in different parts of the application. Sometimes this can be solved by «lifting state up» to parent components, but that doesn’t always help.

One way to solve this is to extract the shared state from the components, and put it into a centralized location outside the component tree. With this, our component tree becomes a big «view», and any component can access the state or trigger actions, no matter where they are in the tree!

By defining and separating the concepts involved in state management and enforcing rules that maintain independence between views and states, we give our code more structure and maintainability.

This is the basic idea behind Redux: a single centralized place to contain the global state in your application, and specific patterns to follow when updating that state to make the code predictable.

Immutability

«Mutable» means «changeable». If something is «immutable», it can never be changed.

JavaScript objects and arrays are all mutable by default. If I create an object, I can change the contents of its fields. If I create an array, I can change the contents as well:

const obj = { a: 1, b: 2 }
// still the same object outside, but the contents have changed
obj.b = 3

const arr = ['a', 'b']
// In the same way, we can change the contents of this array
arr.push('c')
arr[1] = 'd'

This is called mutating the object or array. It’s the same object or array reference in memory, but now the contents inside the object have changed.

In order to update values immutably, your code must make copies of existing objects/arrays, and then modify the copies.

We can do this by hand using JavaScript’s array / object spread operators, as well as array methods that return new copies of the array instead of mutating the original array:

const obj = {
  a: {
    // To safely update obj.a.c, we have to copy each piece
    c: 3
  },
  b: 2
}

const obj2 = {
  // copy obj
  ...obj,
  // overwrite a
  a: {
    // copy obj.a
    ...obj.a,
    // overwrite c
    c: 42
  }
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')

Redux expects that all state updates are done immutably. We’ll look at where and how this is important a bit later, as well as some easier ways to write immutable update logic.

:::info Want to Know More?

For more info on how immutability works in JavaScript, see:

  • A Visual Guide to References in JavaScript
  • Immutability in React and Redux: The Complete Guide

:::

Terminology

There are some important Redux terms that you’ll need to be familiar with before we continue:

Actions

An action is a plain JavaScript object that has a type field. You can think of an action as an event that describes something that happened in the application.

The type field should be a string that gives this action a descriptive name, like "todos/todoAdded". We usually write that type string like "domain/eventName", where the first part is the feature or category that this action belongs to, and the second part is the specific thing that happened.

An action object can have other fields with additional information about what happened. By convention, we put that information in a field called payload.

A typical action object might look like this:

const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}

Action Creators

An action creator is a function that creates and returns an action object. We typically use these so we don’t have to write the action object by hand every time:

const addTodo = text => {
  return {
    type: 'todos/todoAdded',
    payload: text
  }
}

Reducers

A reducer is a function that receives the current state and an action object, decides how to update the state if necessary, and returns the new state: (state, action) => newState. You can think of a reducer as an event listener which handles events based on the received action (event) type.

:::info

«Reducer» functions get their name because they’re similar to the kind of callback function you pass to the Array.reduce() method.

:::

Reducers must always follow some specific rules:

  • They should only calculate the new state value based on the state and action arguments
  • They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values.
  • They must not do any asynchronous logic, calculate random values, or cause other «side effects»

We’ll talk more about the rules of reducers later, including why they’re important and how to follow them correctly.

The logic inside reducer functions typically follows the same series of steps:

  • Check to see if the reducer cares about this action
    • If so, make a copy of the state, update the copy with new values, and return it
  • Otherwise, return the existing state unchanged

Here’s a small example of a reducer, showing the steps that each reducer should follow:

const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === 'counter/increment') {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1
    }
  }
  // otherwise return the existing state unchanged
  return state
}

Reducers can use any kind of logic inside to decide what the new state should be: if/else, switch, loops, and so on.

The Array.reduce() method lets you take an array of values, process each item in the array one at a time, and return a single final result. You can think of it as «reducing the array down to one value».

Array.reduce() takes a callback function as an argument, which will be called one time for each item in the array. It takes two arguments:

  • previousResult, the value that your callback returned last time
  • currentItem, the current item in the array

The first time that the callback runs, there isn’t a previousResult available, so we need to also pass in an initial value that will be used as the first previousResult.

If we wanted to add together an array of numbers to find out what the total is, we could write a reduce callback that looks like this:

const numbers = [2, 5, 8]

const addNumbers = (previousResult, currentItem) => {
  console.log({ previousResult, currentItem })
  return previousResult + currentItem
}

const initialValue = 0

const total = numbers.reduce(addNumbers, initialValue)
// {previousResult: 0, currentItem: 2}
// {previousResult: 2, currentItem: 5}
// {previousResult: 7, currentItem: 8}

console.log(total)
// 15

Notice that this addNumbers «reduce callback» function doesn’t need to keep track of anything itself. It takes the previousResult and currentItem arguments, does something with them, and returns a new result value.

A Redux reducer function is exactly the same idea as this «reduce callback» function! It takes a «previous result» (the state), and the «current item» (the action object), decides a new state value based on those arguments, and returns that new state.

If we were to create an array of Redux actions, call reduce(), and pass in a reducer function, we’d get a final result the same way:

const actions = [
  { type: 'counter/increment' },
  { type: 'counter/increment' },
  { type: 'counter/increment' }
]

const initialState = { value: 0 }

const finalResult = actions.reduce(counterReducer, initialState)
console.log(finalResult)
// {value: 3}

We can say that Redux reducers reduce a set of actions (over time) into a single state. The difference is that with Array.reduce() it happens all at once, and with Redux, it happens over the lifetime of your running app.

Store

The current Redux application state lives in an object called the store .

The store is created by passing in a reducer, and has a method called getState that returns the current state value:

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}

Dispatch

The Redux store has a method called dispatch. The only way to update the state is to call store.dispatch() and pass in an action object. The store will run its reducer function and save the new state value inside, and we can call getState() to retrieve the updated value:

store.dispatch({ type: 'counter/increment' })

console.log(store.getState())
// {value: 1}

You can think of dispatching actions as «triggering an event» in the application. Something happened, and we want the store to know about it. Reducers act like event listeners, and when they hear an action they are interested in, they update the state in response.

We typically call action creators to dispatch the right action:

const increment = () => {
  return {
    type: 'counter/increment'
  }
}

store.dispatch(increment())

console.log(store.getState())
// {value: 2}

Selectors

Selectors are functions that know how to extract specific pieces of information from a store state value. As an application grows bigger, this can help avoid repeating logic as different parts of the app need to read the same data:

const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2

Redux Application Data Flow

Earlier, we talked about «one-way data flow», which describes this sequence of steps to update the app:

  • State describes the condition of the app at a specific point in time
  • The UI is rendered based on that state
  • When something happens (such as a user clicking a button), the state is updated based on what occurred
  • The UI re-renders based on the new state

For Redux specifically, we can break these steps into more detail:

  • Initial setup:
    • A Redux store is created using a root reducer function
    • The store calls the root reducer once, and saves the return value as its initial state
    • When the UI is first rendered, UI components access the current state of the Redux store, and use that data to decide what to render. They also subscribe to any future store updates so they can know if the state has changed.
  • Updates:
    • Something happens in the app, such as a user clicking a button
    • The app code dispatches an action to the Redux store, like dispatch({type: 'counter/increment'})
    • The store runs the reducer function again with the previous state and the current action, and saves the return value as the new state
    • The store notifies all parts of the UI that are subscribed that the store has been updated
    • Each UI component that needs data from the store checks to see if the parts of the state they need have changed.
    • Each component that sees its data has changed forces a re-render with the new data, so it can update what’s shown on the screen

Here’s what that data flow looks like visually:

Redux data flow diagram

What You’ve Learned

Redux does have a number of new terms and concepts to remember. As a reminder, here’s what we just covered:

:::tip Summary

  • Redux is a library for managing global application state
    • Redux is typically used with the React-Redux library for integrating Redux and React together
    • Redux Toolkit is the recommended way to write Redux logic
  • Redux uses a «one-way data flow» app structure
    • State describes the condition of the app at a point in time, and UI renders based on that state
    • When something happens in the app:
      • The UI dispatches an action
      • The store runs the reducers, and the state is updated based on what occurred
      • The store notifies the UI that the state has changed
    • The UI re-renders based on the new state
  • Redux uses several types of code
    • Actions are plain objects with a type field, and describe «what happened» in the app
    • Reducers are functions that calculate a new state value based on previous state + an action
    • A Redux store runs the root reducer whenever an action is dispatched

:::

What’s Next?

We’ve seen each of the individual pieces of a Redux app. Next, continue on to Part 2: Redux App Structure, where we’ll look at a full working example to see how the pieces fit together.

Понравилась статья? Поделить с друзьями:
  • Аквамарис норм инструкция по применению для детей
  • Инсталляция roca dama senso инструкция по установке
  • Инструкция нимесил в порошке 100 мг по применению лекарства взрослым
  • Zastone zt 889g инструкция на русском
  • Что такое мануал выплаты