Предисловие

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

Эту статью нелегко читать. Это действительно для детального понимания рабочего процесса React Redux. Я рекомендую вам просматривать графическую шпаргалку параллельно (распечатка, разделенный экран, второй монитор) во время чтения статьи, чтобы всегда иметь обзор.

Я стараюсь, чтобы статья была как можно более актуальной, однако самая последняя версия всегда будет на GitHub вместе с журналом изменений.

Вступление

React и Redux - действительно впечатляющие разработки, которые определенно влияют на то, как будет осуществляться дизайн Frontend сейчас и в обозримом будущем. Основную концепцию React понять гораздо легче, чем Redux. React имеет дело с компонентами, которые гораздо более вообразимы. Redux, с другой стороны, представляет рабочий процесс, который гораздо менее естественен, особенно потому, что он использует некоторый словарь в своем API, который не всегда интуитивно понятен.

В документации Redux указано, что React не нуждается в Redux и что его не следует использовать, если он не нужен. Но я думаю, что часто бывает наоборот. Redux значительно снижает сложность программирования приложения, поэтому, на мой взгляд, его лучше использовать, чем нет. Или другими словами: по умолчанию следует использовать Redux, и нужно отказаться от него, если он не нужен. Мне действительно нравится думать, что название Redux происходит от REDUce compleXity.

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

1. Frontend - создайте свое первое реальное приложение React.js. Сообщение Макса Штойбера
2. Повышение уровня с помощью React: Redux. Сообщение Брэда Вестфолла
3. Три правила структурирования (Redux) приложений. Серия постов Джека Хсу.
4. Начало работы с Redux. Видеоряд от Дэна Абрамова.
5. Создание приложений на React с идиоматическим Redux. Видеосериал Дэна Абрамова.
6. Learn Redux. Видеоряд от Уэса Боса.
7. Изучите React и Redux. Видеосериал Каталин Лунтрару.

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

Итак, чтобы дополнить существующие руководства, в этой статье описывается концептуальный обзор Redux и его рабочий процесс в приложении React Redux. Описание начинается с доминирующего игрока в Redux Applications - магазина. Как только рабочий процесс будет понят, вам, вероятно, будет намного легче следовать всем вышеперечисленным руководствам.

Пройдя полный круг, эта статья также указывает на некоторые общие внешние библиотеки и то, как они могут быть задействованы: 'immutable', 'normalizr', 'Reselect', 'redux-thunk', 'redux-saga', 'redux -promise »и« redux-persist ».

Вы также можете найти копию этой статьи и шпаргалку в более высоком разрешении по адресу: https://github.com/uanders/react-redux-cheatsheet. Там вы также можете указать мне на предложения, уточнения или исправления.

Графическая шпаргалка

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

Магазин

Основная идея приложения Redux - отделить состояние приложения от самого приложения. Состояние приложения хранится в магазине. Поэтому я различаю состояние приложения, которое находится в магазине. Магазин - это главный игрок в приложении React Redux. Состояние, хранящееся в хранилище, представляет собой простой объект JS со срезами. Так как состояние, таким образом, иерархически содержит другие структуры, оно также называется деревом состояний:

// this uses ES6 shorthand syntax
 state = {
   slice01,
   slice02,
   /* … */
   sliceN
 }

У каждого среза может быть свой тип данных: число, строка, массив и, конечно же, объект. В рамках этого урока я предполагаю, что все срезы относятся к типу object. Для управления состоянием вашего приложения вы обнаружите, что гораздо легче получить доступ к частям этого объекта, если оно нормализовано. Подробнее о нормализации читайте в библиотеке normalizr.

 // bad:
 "users": {
   {"id": "1", "name": "Adam"},
   {"id": "2", "name": "Eve"},
 }
// good:
"users": {
  "1": { "id": "1", "name": "Adam" },
  "2": { "id": "2", "name": "Eve" }
},

Каждый кусочек должен быть неизменным. Это означает, что его нельзя изменить. Если состояние приложения изменяется, старый объект состояния должен быть обновлен до нового объекта состояния, который содержит все, начиная с объекта текущего состояния и обновленного фрагмента.

Чтобы помочь управлять неизменяемостью, а также ускорить обновление, разумно использовать, например, immutable.js. Эта библиотека предоставляет функцию fromJS (), которая генерирует неизменяемый объект из объекта JS. Здесь я предположил, что все фрагменты являются объектами JS, что чаще всего имеет место в сложных приложениях. Но, конечно, вместо этого возможны другие структуры данных.

import { fromJS } from 'immutable'
state = {
  slice01: fromJS(slice01)
  slice02: fromJS(slice02),
  /* ... */
  sliceN: fromJS(SliceN)
}

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

Каждый фрагмент состояния управляется своей собственной функцией, которая отвечает за обновление именно этого фрагмента и копирование всех других фрагментов в новый объект состояния. Эта функция называется reducer (). Имя reducer семантически не имеет большого смысла и имеет чисто техническое происхождение. [Цитата из официальной документации: Это называется редуктором, потому что это тип функции, которую вы передаете в Array.prototype.reduce (reducer,? InitialValue).] Если вы боретесь с именем, переведите его в производитель , потому что его единственная цель - просто создать новое состояние, которое помещается в хранилище.

Это популярное соглашение (но не обязательство) - называть функцию-редуктор после среза, которым она управляет. Я предлагаю вам действительно придерживаться этого соглашения, потому что оно полезно во многих отношениях. Таким образом, редуктор для state.slice01 будет называться slice01 ().

Поскольку обычно существует редуктор для каждого фрагмента, мы получаем множество отдельных редукторов, каждый из которых имеет отношение только к своему фрагменту. Однако нам нужно заботиться обо всех срезах одновременно. Решение состоит в том, чтобы объединить все редукторы фрагментов в один общий редуктор, который часто называют rootReducer (). Для этого существует функция под названием combReducers (), которая принимает объект, содержащий отдельные редукторы:

import { combineReducers } from 'redux'
const rootReducer = function combineReducers({
  slice01,
  slice02,
  /* ... */
  sliceN
})

Обратите внимание, поскольку commonReducers () принимает объект редукторов, вы также можете создавать подмножества редукторов с помощью commonReducers (), а затем объединять эти подмножества в rootReducer () .

Приложение и компоненты

Приложение React Redux содержит компоненты, вложенные в компоненты.

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

Поскольку магазин - это компонент, полностью отдельный от приложения, он должен быть известен приложению. Это достигается путем передачи хранилища от корневого компонента компоненту приложения. Теперь в компоненте App магазин будет доступен через this.props.store.

const Root = ( {store} ) => (
  <App store={store} />
)

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

Есть два типа компонентов: умные (контейнерные) компоненты и тупые (презентационные) компоненты. Умные компоненты имеют состояние, а глупые - нет. Конечно, из этого правила есть исключения. В контексте Redux вы можете интерпретировать, что интеллектуальные компоненты передали состояние, в котором они изначально находились, на аутсорсинг внешнему диспетчеру состояний, который является хранилищем.

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

class SmartComponent01 extends Component {
  manageSomeData () {
    /* ... */
  }
  applySomeBusinessLogic () {
    /* ... */
  }
  handleSomeEvent = () => {
    /* ... */
  }
 
  render() {
    return (
      <div>
        <DumbComponent01 data={/*...*/} />
        <DumbComponent02 data={/*...*/} />
      </div>
    )
  }
}

В качестве примечания, поскольку мы впервые показываем методы класса: помните, что есть два типа обозначений для объявлений методов с различным эффектом для автосвязывания методов для this оператор классов. Здесь вам нужно будет вручную привязать manageSomeData () и makeSomeCalculations () к оператору this, тогда как handleSomeEvent () был бы привязан автоматически.

Поскольку только компоненты смарт-контейнера управляют данными, только им необходимо получать данные о состоянии из хранилища. Это работает с помощью функции под названием connect (), которую вам нужно импортировать из react-redux. Функция connect () возвращает компонент более высокого порядка, то есть компонент, который ожидает компонент в качестве аргумента. Этот аргумент - умный компонент, который вы хотите подключить. Компонент более высокого порядка, созданный функцией connect (), визуализирует интеллектуальный компонент, передавая данные из хранилища в свои свойства.

import {connect} from 'react-redux'
/* ... */
const ConnectedSmartComponent01 = connect()(SmartComponent01) 
export default ConnectedSmartComponent01

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

Таким образом, отображение среза состояния на свойства интеллектуального компонента выполняется с помощью функции, которая условно называется mapStateToProps (). Для каждого подключенного компонента вам нужно будет написать такую ​​функцию, которая будет возвращать данные состояния фрагментов, необходимых для компонента. Итак, в конечном итоге у вас будет множество функций mapStateToProps (), все с тем же именем, но с разным содержанием. Одно и то же имя не является проблемой, поскольку ни одна из функций mapStateToProps () никогда не экспортируется. Итак, чтобы компонент действительно получил данные о состоянии через функцию connect (), вам необходимо передать mapStateToProps () в connect () функция. Таким образом, подключенный компонент подписывается на обновления состояния Redux.

import {connect} from 'react-redux'
function mapStateToProps(state) {
  return {
    // component gets this.props.slice01
    slice01: state.slice01     
  }
}
    
// export without a new name
export default connect(mapStateToProps)(SmartComponent01)

При каждом обновлении состояния в хранилище будут вызываться все ваши функции сопоставления mapStateToProps () для подключения компонентов, и новые данные состояния будут сопоставлены со свойствами подключенных компонентов.

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

function mapStateToProps(state) {
  return {
    // component gets this.props.selection1
    selection1: getSelection1(state.slice01),
    // component gets this.props.selection2   
    selection2: getSelection2(state.slice01)
  }
}

Вернемся к функции connect (). Несмотря на то, что вы правильно импортировали функцию подключения из response-redux, вы можете задаться вопросом, как функция connect () на самом деле может получить доступ к хранилищу во всем приложении, даже если хранилище не передается явно вплоть до всех дочерних компонентов. Ответ находится в компоненте под названием Provider, который вы должны обернуть вокруг своего компонента приложения. Единственная цель Provider - добавить хранилище в контекст компонента App, чтобы все дочерние компоненты могли получить к нему доступ. Контекст - это особенность React для таких редких случаев. С учетом сказанного, функция connect () теперь может обращаться к методам хранилища. Таким образом, компонент Provider заменяет корневой компонент, который мы ввели в начале:

import { Provider } from 'react-redux'
const Root = ( {store} ) => (
  <Provider store={store}>
     <App />
  </Provider>
)

Редукторы и действия

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

Но как тогда вы можете получить доступ к dispatch () из компонента, когда он принадлежит магазину? Ответ кроется в функции connect (). Каждый раз, когда вы подключаете компонент к хранилищу, функция connect () принимает метод store dispatch () и неявно внедряет его в компонент, сопоставляя его с его свойствами. Итак, из подключенного компонента он доступен следующим образом:

this.props.dispatch

Если вы не хотите, чтобы функция connect () просто вставляла стандартную функцию dispatch () и сопоставляла ее с this.props.dispatch, вы можете изменить его в соответствии с вашими потребностями. Это достигается с помощью функции, которая обычно называется mapDispatchToProps (). После его написания вам необходимо передать его функции connect () в качестве второго аргумента.

function mapDispatchToProps(dispatch) {
  return {
    /* your own bindings for the dispatch() function */
  }
}
export default connect(mapStateToProps, 
                       mapDispatchToProps)(SmartComponent01)

Теперь у вашего компонента есть функция dispatch (). Но что такое диспетчерская функция dispatch ()? Диспетчер обычно отправляет кому-то команду. Подумайте о службе 911, где диспетчер приказывает полицейскому что-то предпринять. Или в магазине, где диспетчер приказывает отправить товар покупателю. В Redux вам нужны разные команды, но все они связаны с обновлением состояния в магазине. Поэтому каждая команда должна содержать два типа информации. Тип, представляющий команду и данные, относящиеся к изменению состояния. Эта команда называется действием в Redux. Название действие может сначала сбить с толку, так как с этим именем можно было бы ожидать скорее функцию, чем объект. Однако со временем к этому привыкаешь. action - это простой объект JS, содержащий тип и, в идеале, минимальный объем релевантных данных, необходимых для выполнения изменения состояния.

const DO_SOMETHING = 'DO_SOMETHING'
// an action object
{
  type: DO_SOMETHING,
  payload: relevantData
}

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

// relevant data
let payload = 
  {
    id,
    something
  }

Работать с объектами действия не так удобно, поэтому обычно используется функция, возвращающая объект действия. Эта функция называется actionCreator (). Название немного напыщенное, потому что функция на самом деле мало что создает, она просто возвращает объект действия - вот и все.

// this simple function is called an actionCreator
function do_something(payload) {
  return (
    {
       type: DO_SOMETHING, 
       payload
    }
  )
}

Если функция dispatch () теперь отправляет команду только в форме объекта, какая функция тогда фактически принимает эту команду и занимается обновлением хранилища? Мы возвращаемся к нашим функциям редуктора, которые мы представили ранее. Помните, что цель функций редуктора - создать новый фрагмент состояния, если в этом фрагменте есть изменения. Это означает, что функция dispatch () должна вызывать функцию редуктора. Именно это и происходит в коде Redux. Таким образом, в теле функции функция dispatch () вызывает функцию rootReducer () и передает команду, что делать, в форме действия объект в качестве второго аргумента.

А первый аргумент? Очевидно, что rootReducer () требуется два аргумента для возврата нового состояния: rootReducer = (state, action) = ›newState. Первый аргумент - это текущее состояние в магазине, а второй аргумент представляет его изменения. И откуда теперь rootReducer () получает текущее состояние? Что ж, снова из функции dispatch (). Сначала он получает текущее состояние из хранилища с помощью метода store.getState (), а затем передает текущее состояние в rootReducer () в качестве первого аргумента вместе с действие в качестве второго аргумента.

rootReducer () берет полученное действие и передает его всем своим дочерним редукторам, однако не со всем состоянием, а только с соответствующим ему фрагментом состояния: sliceReducer = (slice, действие) = ›newSlice. [Обратите внимание, что кроме Документации Redux читается (previousState, action) = ›newState, редуктор не получает все состояние по умолчанию, а только его часть состояния.] Каждый редуктор слайса сравнивает получил action.type для случаев, которые он имеет в теле функции. Если совпадений не найдено, возвращается текущее состояние среза и ничего не меняется. Если есть совпадение, он вычисляет обновление соответствующего среза и возвращает его, так что может быть сгенерировано общее новое состояние с обновленным срезом.

// reducer for state.slice01
// using setIn() from immutable.js
function slice01(slice01 = {}, action) {
  switch (action.type) {
    case DO_SOMETHING:
      return slice01.setIn(['somewhere', action.payload.id], 
                           action.payload.something)
    default:
      return slice01
  }
}

Таким образом, магазин получает новое состояние. Когда магазин обновляется новым состоянием, запускается повторный рендеринг компонентов, которые подписались на магазин.

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

В хранилище может быть зарегистрировано произвольное количество функций, которые также будут запускаться всякий раз, когда хранилище обновляется с новым состоянием. Функции, зарегистрированные в хранилище для запуска, были названы слушателями, вероятно, потому, что они слушают обновления состояния. Но на самом деле они не слушают себя активно. Напротив, они не контролируют активно, они просто пассивно запускаются. Функция запуска - это метод, принадлежащий хранилищу с именем store.subscribe (). К этому имени также нужно немного привыкнуть, поскольку сам магазин фактически ни на что не подписывается. Итак, мысленно вы можете подумать trigger, когда используете subscribe (). Не путайте компонент, который на самом деле подписывается на изменение состояния с помощью функции connect () с функцией, которую можно зарегистрировать с помощью store.subscribe ( ), чтобы он запускался всякий раз, когда появляется новое состояние.

store.subscribe(listener)

Это завершает цикл.

Округлять

Несмотря на то, что мы начали эту статью с магазина, мы не говорили о том, как его создать. Это произошло потому, что нам нужно было сначала представить концепцию редукторов. Теперь, когда мы знаем, что такое редукторы, мы можем настроить хранилище с помощью createStore (). Как минимум, хранилищу нужен один аргумент - rootReducer (). Почему? Помните, что позже функция store.dispatch () вызывает rootReducer (), поэтому ей необходимо это знать. В качестве необязательного второго аргумента вы можете предварительно заполнить магазин начальным состоянием:

import { createStore } from 'redux'
    
let store = createStore(rootReducer, initialState)

Если вы хотите сохранить хранилище в локальном хранилище или повторно гидратировать хранилище при перезапуске приложения, возможно, стоит взглянуть на redux-persist. Эта библиотека также помогает выполнять неизменяемые преобразования и шифровать данные в локальном хранилище.

Иногда вы хотите включить дополнительные функции всякий раз, когда команда действия выдается функцией dispatch (). Это может быть, например, функция регистратора или планировщик тайм-аута. Для этой цели Redux предлагает интерфейс для так называемого промежуточного программного обеспечения. По промежуточного слоя выполняется после dispatch () и перед функцией rootReducer (). Это означает, что каждый раз, когда функция dispatch () выдает команду действия, промежуточное ПО выполняется до того, как rootReducer () приступит к работе. Чтобы настроить это, в createStore () есть третий необязательный аргумент, называемый hancer. Этот аргумент ожидает функцию, которая содержит отдельные функции промежуточного программного обеспечения. Redux предоставляет такую ​​функцию под названием applyMiddleware (). Теперь вы можете передать свои улучшения в качестве аргументов в applyMiddleware (), и они будут зарегистрированы так, что они будут выполняться каждый раз при вызове функции dispatch (). Очевидно, что каждая функция промежуточного программного обеспечения должна соответствовать спецификации функции промежуточного программного обеспечения, поскольку выходные данные первой функции промежуточного программного обеспечения становятся входными данными для следующей. Последовательность их выполнения слева направо. Последняя выполняемая функция - это сама функция dispatch ().

import { createStore, applyMiddleware } from 'redux'
let store = createStore(
  rootReducer,
  applyMiddleware(logger, timeoutScheduler)
)

Многие расширения Redux используют эту функциональность промежуточного программного обеспечения. Если вы хотите разместить дополнительное промежуточное ПО поверх, например, applyMiddleware () вы можете использовать служебную функцию compose ().

let store = createStore(
  rootReducer,
  compose(
    applyMiddleware(logger, timeoutScheduler),
    middlewareFromAnExtension()
  )
)

React и Redux синхронны по своей природе, это означает, что они выполняют свои задачи последовательно. Но что произойдет, если dispatch () выдает команду действия корневому редуктору, но соответствующие данные не доступны сразу, и вам нужно дождаться их прибытия из внешнего API? Или если вы хотите dispatch () выполнить команду действия, данные которой основаны на обещании? Для этих случаев Redux имеет дополнительные библиотеки, такие как redux-thunk, redux-prom или redux-saga. Все они являются промежуточным программным обеспечением для функции dispatch () и помогают вам справляться с такими случаями.

Поскольку приложения React отображаются на стороне клиента, изначально в приложении нет URL-адресов. Тем не менее, имеет смысл иметь URL-адреса и ссылаться на определенные ссылки в вашем приложении. Это достигается с помощью пакета под названием response-router. Я имею в виду ≥v4 реагирующего маршрутизатора, поскольку в этой версии произошло довольно много обновлений API по сравнению с предыдущими версиями. По сути, response-router предлагает компонент Router, который поставляется вместе с множеством функций для управления маршрутизацией в вашем приложении. Если вы хотите иметь функцию маршрутизации в своем приложении, концептуально проще всего просто обернуть компонент Router вокруг вашего приложения.

import { Provider } from 'react-redux'
import { BrowserRouter as Router } from 'react-router-dom'

const Root = ( {store} ) => (
  <Provider store={store}>
    <Router>
      <App />
    </Router>
  </Provider>
)

Затем в компоненте приложения вы можете использовать связывание и маршрутизацию с помощью компонентов NavLink и Route:

import { Provider } from 'react-redux'
import { NavLink, Route } from 'react-router-dom'

const App = () => (
  <div>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/about">About</NavLink>
    <NavLink to="/users">Users</NavLink>

    <Route exact path="/" component={Home} />
    <Route path="/about" component={About} />
    <Route path="/users" component={Users} />
  </div>
)

Подведение итогов

С учетом всего сказанного теперь вы можете полностью понять рабочий цикл приложения React Redux. Мы начали объяснение с магазина, но давайте посмотрим, что произойдет, когда мы действительно начнем в приложении. В приложении действие пользователя генерирует событие. Обработчик событий вызывает функцию dispatch (), которая отправляет текущее состояние и действие (объект) в rootReducer (). Объект действия содержит соответствующие данные для запрошенного изменения состояния среза. rootReducer () интерпретирует action.type, обрабатывает данные и генерирует новое состояние. После того, как магазин получил новое состояние, он запускает повторный рендеринг приложения React Redux. Он также запускает выполнение всех функций прослушивателя, которые зарегистрированы с помощью метода subscribe () в хранилище. Кроме того, все компоненты, подписанные с помощью connect (mapStateToProps) на хранилище, теперь получают новые данные о состоянии, как определено в mapStateToProps ().

Я надеюсь, что это пошаговое руководство немного помогло понять Redux и побудит вас использовать его.