С тех пор как я начал работать с ReactJS в Creative-Tim, я использовал его только для создания простых приложений для реагирования или шаблонов, если хотите. Я использовал ReactJS только с create-react-app и никогда не пытался интегрировать его с чем-то еще.

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

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

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

  • Я собираюсь использовать [email protected]. 1 (установлен глобально)
  • Я использую [email protected]
  • Моя версия Node.js на момент написания этого поста была 10.13.0 (LTS)
  • Если вы хотите использовать вместо этого Webpack, то вы можете прочитать мою статью о Webpack и объединить то, что я вам здесь показываю, с тем, что я собираюсь показать вам здесь.

Создание нового проекта на основе ReactJS и добавление в него Redux

Прежде всего, давайте создадим новое приложение для реагирования, войдем в него и запустим.

create-react-app react-redux-tutorial
cd react-redux-tutorial
npm start

Как мы видим, приложение create-react-app дает нам очень простой шаблон с абзацем, привязкой к веб-сайту React и официальным вращающимся значком ReactJS.

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

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

Итак, давайте продолжим и добавим следующие пакеты Redux:

npm install --save redux react-redux

Redux v4.0.1

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

React-redux v5.1.1

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

ПРИМЕЧАНИЕ: Если у вас возникли проблемы с приведенной выше командой, попробуйте установить пакеты отдельно

При работе с Redux вам понадобятся три основных вещи:

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

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

1 - Команды Linux / Mac

mkdir src/actions
touch src/actions/startAction.js
touch src/actions/stopAction.js

2 - Команды Windows

mkdir src\actions
echo "" > src\actions\startAction.js
echo "" > src\actions\stopAction.js

Теперь давайте отредактируем src / actions / startAction.js следующим образом:

export const startAction = {
  type: "rotate",
  payload: true
};

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

Теперь давайте отредактируем src / actions / stopAction.js следующим образом:

export const stopAction = {
  type: "rotate",
  payload: false
};

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

Давайте также создадим редуктор для нашего приложения:

1 - Команды Linux / Mac

mkdir src/reducers
touch src/reducers/rotateReducer.js

2 - Команды Windows

mkdir src\reducers
echo "" > src\reducers\rotateReducer.js

И добавьте в него следующий код:

export default (state, action) => {
  switch (action.type) {
    case "rotate":
      return {
        rotating: action.payload
      };
    default:
      return state;
  }
};

Таким образом, редуктор получит оба наших действия, оба имеют тип rotate, и оба они изменят одно и то же состояние в приложении - state.rotating. В зависимости от полезной нагрузки этих действий state.rotating изменится на true или false.

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

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

Итак, как говорится, мы собираемся запустить эту команду:

1 - команда Linux / Mac

touch src/store.js

2 - команда Windows

echo "" > src\store.js

А также добавьте в него следующий код:

import { createStore } from "redux";
import rotateReducer from "reducers/rotateReducer";
function configureStore(state = { rotating: true }) {
  return createStore(rotateReducer,state);
}
export default configureStore;

Итак, мы создаем функцию с именем configureStore, в которой мы отправляем состояние по умолчанию, и мы создаем наше хранилище, используя созданный редуктор и состояние по умолчанию.

Я не уверен, видели ли вы мой импорт, они используют абсолютные пути, поэтому у вас могут быть ошибки из-за этого. Исправление для этого - одно из двух:

Или

1 - Добавьте файл .env в свое приложение следующим образом:

echo "NODE_PATH=./src" > .env

Or

2 - Установите cross-env глобально и измените сценарий запуска из файла package.json следующим образом:

npm install -g cross-env

И внутри package.json

"start": "NODE_PATH=./src react-scripts start",

Теперь, когда мы настроили наше хранилище, наши действия и наш редуктор, нам нужно добавить новый класс в файл src / App.css. Этот класс приостановит вращающуюся анимацию логотипа.

Итак, мы собираемся написать следующее внутри src / App.css:

.App-logo-paused {
  animation-play-state: paused;
}

Итак, ваш файл App.css должен выглядеть примерно так (если вы не используете ту же версию create-response-app, просто скопируйте приведенный ниже код):

.App {
  text-align: center;
}
.App-logo {
  animation: App-logo-spin infinite 20s linear;
  height: 40vmin;
}
/* new class here */
.App-logo-paused {
  animation-play-state: paused;
}
.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}
.App-link {
  color: #61dafb;
}
@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

Теперь нам нужно только изменить наш файл src / App.js, чтобы он прослушивал состояние нашего хранилища. А при нажатии на логотип вызывает одно из действий запуска или остановки.

Прежде всего, нам нужно подключить наш компонент к нашему хранилищу redux, поэтому мы импортируем connect из response-redux.

import { connect } from "react-redux";

После этого мы экспортируем наш компонент приложения с помощью метода подключения следующим образом:

export default connect()(App);

Чтобы изменить состояние хранилища redux, нам потребуются действия, которые мы сделали ранее, поэтому давайте также импортируем их:

import { startAction } from "actions/startAction";
import { stopAction } from "actions/stopAction";

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

Это будет сделано с помощью функции подключения, которая принимает два параметра:

  • mapStateToProps: используется для получения состояния хранилища.
  • mapDispatchToProps: используется для получения действий и отправки их в магазин.

Подробнее о них можно прочитать здесь: аргументы функции react-redux connect.

Итак, давайте напишем внутри нашего App.js (если можно, в конце файла):

const mapStateToProps = state => ({
  ...state
});
const mapDispatchToProps = dispatch => ({
  startAction: () => dispatch(startAction),
  stopAction: () => dispatch(stopAction)
});

После этого давайте добавим их в нашу функцию подключения следующим образом:

export default connect(mapStateToProps, mapDispatchToProps)(App);

И прямо сейчас внутри нашего компонента App мы можем получить доступ к состоянию хранилища, startAction и stopAction через props.

Давайте изменим тег img на:

<img 
  src={logo} 
  className={
    "App-logo" + 
    (this.props.rotating ? "":" App-logo-paused")
  } 
  alt="logo" 
  onClick={
    this.props.rotating ? 
      this.props.stopAction : this.props.startAction
  }
/>

Итак, мы говорим, что если состояние вращения хранилища (this.props.rotating) истинно, то нам нужен только логотип приложения className для нашего img. Если это неверно, мы также хотим, чтобы класс App-logo-paused был установлен в className. Таким образом мы приостанавливаем анимацию.

Кроме того, если this.props.rotating имеет значение true, мы хотим отправить в наш магазин функцию onClick и снова изменить ее на ложный, и наоборот.

Мы почти закончили, но кое-что забыли.

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

Для этого мы заходим в src / index.js, импортируем Provider из response-redux и вновь созданное хранилище следующим образом:

import { Provider } from "react-redux";
import configureStore from "store";
  • Provider: делает хранилище Redux доступным для любых вложенных компонентов, которые были заключены в функцию подключения.

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

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

Здесь мы могли бы использовать функцию configureStore с другим состоянием, например configureStore ({rotating: false}).

Итак, ваш index.js должен выглядеть так:

import React from 'react';
import ReactDOM from 'react-dom';
// new imports start
import { Provider } from "react-redux";
import configureStore from "store";
// new imports stop
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
// changed the render
ReactDOM.render(
  <Provider store={configureStore()}>
    <App />
  </Provider>,
  document.getElementById('root')
);
// changed the render
serviceWorker.unregister();

Давайте посмотрим, работает ли наше приложение redux:

Использование создателей действий

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

Таким образом, мы можем объединить два наших действия в одну функцию и немного сократить наш код.

Итак, давайте продолжим и создадим новый файл:

1 - команда Linux / Mac

touch src/actions/rotateAction.js

2 - команда Windows

echo "" > src\actions\rotateAction.js

И добавьте этот код:

const rotateAction = (payload) => {
  return {
    type: "rotate",
    payload
  }
}
export default rotateAction;

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

Внутри компонента src / App.js нам нужно импортировать наш новый создатель действий:

import rotateAction from "actions/rotateAction";

Добавьте новую функцию в mapDispatchToProps следующим образом:

rotateAction: получит (полезную нагрузку) и отправит rotateAction с полезной нагрузкой

Измените функцию onClick на:

onClick={() => this.props.rotateAction(!this.props.rotating)}

И, наконец, добавьте нашего нового создателя действий в mapDispatchToProps следующим образом:

rotateAction: (payload) => dispatch(rotateAction(payload))

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

Вот как должен выглядеть ваш новый src / App.js:

import React, { Component } from 'react';
// new lines from here
import { connect } from "react-redux";
import rotateAction from "actions/rotateAction";
//// new lines to here
import logo from './logo.svg';
import './App.css';
class App extends Component {
  render() {
    console.log(this.props);
    return (
      <div className="App">
        <header className="App-header">
          <img
            src={logo}
            className={
              "App-logo" +
              (this.props.rotating ? "":" App-logo-paused")
            }
            alt="logo"
            onClick={
              () => this.props.rotateAction(!this.props.rotating)
            }
          />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}
const mapStateToProps = state => ({
  ...state
});
const mapDispatchToProps = dispatch => ({
  rotateAction: (payload) => dispatch(rotateAction(payload))
});
export default connect(mapStateToProps, mapDispatchToProps)(App);

Реальный пример с Paper Dashboard React

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

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

Вы можете получить его тремя способами:

  1. Скачать с creative-tim.com
  2. Скачать с Github
  3. Клон из Github:
git clone https://github.com/creativetimofficial/paper-dashboard-react.git

Теперь, когда у нас есть этот продукт, давайте войдем в него cd и снова установим redux и react-redux:

npm install --save redux react-redux

После этого нам нужно создать действия. Поскольку в правом меню у нас есть 2 цвета, которые задают фон левого меню, и 5 цветов, которые изменяют цвет ссылок, нам нужно 7 действий или 2 создателя действий - и мы выберем этот второй вариант, поскольку он немного меньше кода для написания:

1 - Команды Linux / Mac

mkdir src/actions
touch src/actions/setBgAction.js
touch src/actions/setColorAction.js

2 - Команды Windows

mkdir src\actions
echo "" > src\actions\setBgAction.js
echo "" > src\actions\setColorAction.js

После этого создадим следующий код действий:

- src / actions / setBgAction.js

const setBgAction = (payload) => {
  return {
    type: "bgChange",
    payload
  }
}
export default setBgAction;

- src / actions / setColorAction.js

const setColorAction = (payload) => {
  return {
    type: "colorChange",
    payload
  }
}
export default setColorAction;

Теперь, как и в первой части, нам понадобится редуктор:

1 - Команды Linux / Mac

mkdir src/reducers
touch src/reducers/rootReducer.js

2 - Команды Windows

mkdir src\reducers
echo "" > src\reducers\rootReducer.js

И код редуктора:

export default (state, action) => {
  switch (action.type) {
    case "bgChange":
      return {
        ...state,
        bgColor: action.payload
      };
    case "colorChange":
      return {
        ...state,
        activeColor: action.payload
      };
    default:
      return state;
  }
};

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

Еще нам понадобится магазин:

1 - команда Linux / Mac

touch src/store.js

2 - команда Windows

echo "" > src\store.js

Код для этого:

import { createStore } from "redux";
import rootReducer from "reducers/rootReducer";
function configureStore(state = { bgColor: "black", activeColor: "info" }) {
  return createStore(rootReducer,state);
}
export default configureStore;

Внутри src / index.js нам понадобятся:

// new imports start
import { Provider } from "react-redux";
import configureStore from "store";
// new imports stop

А также измените функцию рендеринга:

ReactDOM.render(
  <Provider store={configureStore()}>
    <Router history={hist}>
      <Switch>
        {indexRoutes.map((prop, key) => {
          return <Route path={prop.path} key={key} component={prop.component} />;
        })}
      </Switch>
    </Router>
  </Provider>,
  document.getElementById("root")
);

Итак, файл index.js должен выглядеть так:

import React from "react";
import ReactDOM from "react-dom";
import { createBrowserHistory } from "history";
import { Router, Route, Switch } from "react-router-dom";
// new imports start
import { Provider } from "react-redux";
import configureStore from "store";
// new imports stop
import "bootstrap/dist/css/bootstrap.css";
import "assets/scss/paper-dashboard.scss";
import "assets/demo/demo.css";
import indexRoutes from "routes/index.jsx";
const hist = createBrowserHistory();
ReactDOM.render(
  <Provider store={configureStore()}>
    <Router history={hist}>
      <Switch>
        {indexRoutes.map((prop, key) => {
          return <Route path={prop.path} key={key} component={prop.component} />;
        })}
      </Switch>
    </Router>
  </Provider>,
  document.getElementById("root")
);

Теперь нам нужно внести некоторые изменения в src / layouts / Dashboard / Dashboard.jsx. Нам нужно удалить состояние и функции, которые его изменяют. Так что продолжайте и удалите эти фрагменты кода:

Конструктор (между строками 16 и 22):

constructor(props){
  super(props);
  this.state = {
    backgroundColor: "black",
    activeColor: "info",
  }
}

Государственные функции (между строками 41 и 46):

handleActiveClick = (color) => {
    this.setState({ activeColor: color });
  }
handleBgClick = (color) => {
  this.setState({ backgroundColor: color });
}

Свойства боковой панели bgColor и activeColor (строки 53 и 54):

bgColor={this.state.backgroundColor}
activeColor={this.state.activeColor}

Все свойства FixedPlugin (между строками 59–62):

bgColor={this.state.backgroundColor}
activeColor={this.state.activeColor}
handleActiveClick={this.handleActiveClick}
handleBgClick={this.handleBgClick}

Итак, мы остаемся с этим кодом внутри компонента макета Dashboard:

import React from "react";
// javascript plugin used to create scrollbars on windows
import PerfectScrollbar from "perfect-scrollbar";
import { Route, Switch, Redirect } from "react-router-dom";
import Header from "components/Header/Header.jsx";
import Footer from "components/Footer/Footer.jsx";
import Sidebar from "components/Sidebar/Sidebar.jsx";
import FixedPlugin from "components/FixedPlugin/FixedPlugin.jsx";
import dashboardRoutes from "routes/dashboard.jsx";
var ps;
class Dashboard extends React.Component {
  componentDidMount() {
    if (navigator.platform.indexOf("Win") > -1) {
      ps = new PerfectScrollbar(this.refs.mainPanel);
      document.body.classList.toggle("perfect-scrollbar-on");
    }
  }
  componentWillUnmount() {
    if (navigator.platform.indexOf("Win") > -1) {
      ps.destroy();
      document.body.classList.toggle("perfect-scrollbar-on");
    }
  }
  componentDidUpdate(e) {
    if (e.history.action === "PUSH") {
      this.refs.mainPanel.scrollTop = 0;
      document.scrollingElement.scrollTop = 0;
    }
  }
  render() {
    return (
      <div className="wrapper">
        <Sidebar
          {...this.props}
          routes={dashboardRoutes}
        />
        <div className="main-panel" ref="mainPanel">
          <Header {...this.props} />
          <Switch>
            {dashboardRoutes.map((prop, key) => {
              if (prop.pro) {
                return null;
              }
              if (prop.redirect) {
                return <Redirect from={prop.path} to={prop.pathTo} key={key} />;
              }
              return (
                <Route path={prop.path} component={prop.component} key={key} />
              );
            })}
          </Switch>
          <Footer fluid />
        </div>
        <FixedPlugin />
      </div>
    );
  }
}
export default Dashboard;

Нам нужно подключить к магазину компоненты Боковая панель и FixedPlugin.

Для src / components / Sidebar / Sidebar.jsx:

import { connect } from "react-redux";

И измените экспорт на:

const mapStateToProps = state => ({
  ...state
});
export default connect(mapStateToProps)(Sidebar);

Для src / components / FixedPlugin / FixedPlugin.jsx:

import { connect } from "react-redux";
import setBgAction from "actions/setBgAction";
import setColorAction from "actions/setColorAction";

И теперь экспорт должен быть:

const mapStateToProps = state => ({
  ...state
});
const mapDispatchToProps = dispatch => ({
  setBgAction: (payload) => dispatch(setBgAction(payload)),
  setColorAction: (payload) => dispatch(setColorAction(payload))
});
export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);

У нас будут следующие изменения:

  • где бы вы ни встретили слово handleBgClick, вам нужно будет заменить его на setBgAction.
  • где бы вы ни встретили слово handleActiveClick, вам нужно будет заменить его на setColorAction.

Итак, компонент FixedPlugin теперь должен выглядеть так:

import React, { Component } from "react";
import { connect } from "react-redux";
import setBgAction from "actions/setBgAction";
import setColorAction from "actions/setColorAction";
import Button from "components/CustomButton/CustomButton.jsx";
class FixedPlugin extends Component {
  constructor(props) {
    super(props);
    this.state = {
      classes: "dropdown show"
    };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    if (this.state.classes === "dropdown") {
      this.setState({ classes: "dropdown show" });
    } else {
      this.setState({ classes: "dropdown" });
    }
  }
  render() {
    return (
      <div className="fixed-plugin">
        <div className={this.state.classes}>
          <div onClick={this.handleClick}>
            <i className="fa fa-cog fa-2x" />
          </div>
          <ul className="dropdown-menu show">
            <li className="header-title">SIDEBAR BACKGROUND</li>
            <li className="adjustments-line">
              <div className="badge-colors text-center">
                <span
                  className={
                    this.props.bgColor === "black"
                      ? "badge filter badge-dark active"
                      : "badge filter badge-dark"
                  }
                  data-color="black"
                  onClick={() => {
                    this.props.setBgAction("black");
                  }}
                />
                <span
                  className={
                    this.props.bgColor === "white"
                      ? "badge filter badge-light active"
                      : "badge filter badge-light"
                  }
                  data-color="white"
                  onClick={() => {
                    this.props.setBgAction("white");
                  }}
                />
              </div>
            </li>
            <li className="header-title">SIDEBAR ACTIVE COLOR</li>
            <li className="adjustments-line">
              <div className="badge-colors text-center">
                <span
                  className={
                    this.props.activeColor === "primary"
                      ? "badge filter badge-primary active"
                      : "badge filter badge-primary"
                  }
                  data-color="primary"
                  onClick={() => {
                    this.props.setColorAction("primary");
                  }}
                />
                <span
                  className={
                    this.props.activeColor === "info"
                      ? "badge filter badge-info active"
                      : "badge filter badge-info"
                  }
                  data-color="info"
                  onClick={() => {
                    this.props.setColorAction("info");
                  }}
                />
                <span
                  className={
                    this.props.activeColor === "success"
                      ? "badge filter badge-success active"
                      : "badge filter badge-success"
                  }
                  data-color="success"
                  onClick={() => {
                    this.props.setColorAction("success");
                  }}
                />
                <span
                  className={
                    this.props.activeColor === "warning"
                      ? "badge filter badge-warning active"
                      : "badge filter badge-warning"
                  }
                  data-color="warning"
                  onClick={() => {
                    this.props.setColorAction("warning");
                  }}
                />
                <span
                  className={
                    this.props.activeColor === "danger"
                      ? "badge filter badge-danger active"
                      : "badge filter badge-danger"
                  }
                  data-color="danger"
                  onClick={() => {
                    this.props.setColorAction("danger");
                  }}
                />
              </div>
            </li>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-react"
                color="primary"
                block
                round
              >
                Download now
              </Button>
            </li>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-react/#/documentation/tutorial"
                color="default"
                block
                round
                outline
              >
                <i className="nc-icon nc-paper"></i> Documentation
              </Button>
            </li>
            <li className="header-title">Want more components?</li>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-pro-react"
                color="danger"
                block
                round
                disabled
              >
                Get pro version
              </Button>
            </li>
          </ul>
        </div>
      </div>
    );
  }
}
const mapStateToProps = state => ({
  ...state
});
const mapDispatchToProps = dispatch => ({
  setBgAction: (payload) => dispatch(setBgAction(payload)),
  setColorAction: (payload) => dispatch(setColorAction(payload))
});
export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);

И все готово, можете запустить проект и посмотреть, как все работает нормально:

Несколько редукторов

Поскольку у вас может быть несколько действий, у вас может быть несколько редукторов. Единственное, что вам нужно их объединить - мы увидим это чуть ниже.

Давайте продолжим и создадим два новых редуктора для нашего приложения: один для setBgAction и один для setColorAction:

1 - Команды Linux / Mac

touch src/reducers/bgReducer.js
touch src/reducers/colorReducer.js

2 - Команды Windows

echo "" > src\reducers\bgReducer.js
echo "" > src\reducers\colorReducer.js

После этого давайте создадим код редукторов следующим образом:

- src / redurs / bgReducer.js

export default (state = {}, action) => {
  switch (action.type) {
    case "bgChange":
      return {
        ...state,
        bgColor: action.payload
      };
    default:
      return state;
  }
};

- src / reducers / colorReducer.js.

export default (state = {} , action) => {
  switch (action.type) {
    case "colorChange":
      return {
        ...state,
        activeColor: action.payload
      };
    default:
      return state;
  }
};

При работе с комбинированными редукторами вам необходимо добавить состояние по умолчанию в каждый из ваших редукторов, которые вы собираетесь объединить. В моем случае я выбрал пустой объект, например state = {};

А теперь наш rootReducer объединит эти два элемента следующим образом:

- src / redurs / rootReducer.js

import { combineReducers } from 'redux';
import bgReducer from 'reducers/bgReducer';
import colorReducer from 'reducers/colorReducer';
export default combineReducers({
  activeState: colorReducer,
  bgState: bgReducer
});

Итак, мы говорим, что хотим, чтобы на colorReducer ссылалось свойство activeState состояния приложения, а на bgReducer - bgState - свойство состояния приложения.

Это означает, что наше состояние больше не будет выглядеть так:

state = {
  activeColor: "color1",
  bgColor: "color2"
}

Теперь это будет выглядеть так:

state = {
  activeState: {
    activeColor: "color1"
  },
  bgState: {
    bgColor: "color2"
  }
}

Поскольку мы изменили наши редукторы, теперь мы объединили их вместе в один, нам также нужно изменить наш store.js:

- src / store.js

import { createStore } from "redux";
import rootReducer from "reducers/rootReducer";
// we need to pass the initial state with the new look
function configureStore(state = { bgState: {bgColor: "black"}, activeState: {activeColor: "info"} }) {
  return createStore(rootReducer,state);
}
export default configureStore;

Поскольку мы изменили внешний вид состояния, теперь нам нужно изменить свойства внутри компонентов Боковая панель и FixedPlugin на новый объект состояния:

- src / components / Sidebar / Sidebar.jsx:

Измените строку 36 с

<div className="sidebar" data-color={this.props.bgColor} data-active-color={this.props.activeColor}>

to

<div className="sidebar" data-color={this.props.bgState.bgColor} data-active-color={this.props.activeState.activeColor}>

- src / components / FixedPlugin / FixedPlugin.jsx:

Нам нужно изменить все this.props.bgColor на this.props.bgState.bgColor . И все от this.props.activeColor до this.props.activeState.activeColor.

Итак, новый код должен выглядеть так:

import React, { Component } from "react";
import Button from "components/CustomButton/CustomButton.jsx";
import { connect } from "react-redux";
import setBgAction from "actions/setBgAction";
import setColorAction from "actions/setColorAction";
class FixedPlugin extends Component {
  constructor(props) {
    super(props);
    this.state = {
      classes: "dropdown show"
    };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    if (this.state.classes === "dropdown") {
      this.setState({ classes: "dropdown show" });
    } else {
      this.setState({ classes: "dropdown" });
    }
  }
  render() {
    return (
      <div className="fixed-plugin">
        <div className={this.state.classes}>
          <div onClick={this.handleClick}>
            <i className="fa fa-cog fa-2x" />
          </div>
          <ul className="dropdown-menu show">
            <li className="header-title">SIDEBAR BACKGROUND</li>
            <li className="adjustments-line">
              <div className="badge-colors text-center">
                <span
                  className={
                    this.props.bgState.bgColor === "black"
                      ? "badge filter badge-dark active"
                      : "badge filter badge-dark"
                  }
                  data-color="black"
                  onClick={() => {
                    this.props.setBgAction("black");
                  }}
                />
                <span
                  className={
                    this.props.bgState.bgColor === "white"
                      ? "badge filter badge-light active"
                      : "badge filter badge-light"
                  }
                  data-color="white"
                  onClick={() => {
                    this.props.setBgAction("white");
                  }}
                />
              </div>
            </li>
            <li className="header-title">SIDEBAR ACTIVE COLOR</li>
            <li className="adjustments-line">
              <div className="badge-colors text-center">
                <span
                  className={
                    this.props.activeState.activeColor === "primary"
                      ? "badge filter badge-primary active"
                      : "badge filter badge-primary"
                  }
                  data-color="primary"
                  onClick={() => {
                    this.props.setColorAction("primary");
                  }}
                />
                <span
                  className={
                    this.props.activeState.activeColor === "info"
                      ? "badge filter badge-info active"
                      : "badge filter badge-info"
                  }
                  data-color="info"
                  onClick={() => {
                    this.props.setColorAction("info");
                  }}
                />
                <span
                  className={
                    this.props.activeState.activeColor === "success"
                      ? "badge filter badge-success active"
                      : "badge filter badge-success"
                  }
                  data-color="success"
                  onClick={() => {
                    this.props.setColorAction("success");
                  }}
                />
                <span
                  className={
                    this.props.activeState.activeColor === "warning"
                      ? "badge filter badge-warning active"
                      : "badge filter badge-warning"
                  }
                  data-color="warning"
                  onClick={() => {
                    this.props.setColorAction("warning");
                  }}
                />
                <span
                  className={
                    this.props.activeState.activeColor === "danger"
                      ? "badge filter badge-danger active"
                      : "badge filter badge-danger"
                  }
                  data-color="danger"
                  onClick={() => {
                    this.props.setColorAction("danger");
                  }}
                />
              </div>
            </li>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-react"
                color="primary"
                block
                round
              >
                Download now
              </Button>
            </li>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-react/#/documentation/tutorial"
                color="default"
                block
                round
                outline
              >
                <i className="nc-icon nc-paper"></i> Documentation
              </Button>
            </li>
            <li className="header-title">Want more components?</li>
            <li className="button-container">
              <Button
                href="https://www.creative-tim.com/product/paper-dashboard-pro-react"
                color="danger"
                block
                round
                disabled
              >
                Get pro version
              </Button>
            </li>
          </ul>
        </div>
      </div>
    );
  }
}
const mapStateToProps = state => ({
  ...state
});
const mapDispatchToProps = dispatch => ({
  setBgAction: (payload) => dispatch(setBgAction(payload)),
  setColorAction: (payload) => dispatch(setColorAction(payload))
});
export default connect(mapStateToProps, mapDispatchToProps)(FixedPlugin);

Давайте снова откроем проект с npm start и посмотрим, как все работает. Да да!

Спасибо за прочтение!

Если вам понравилось читать это руководство, дайте ему аплодисменты. Я очень хочу услышать ваши мысли по этому поводу. Просто оставьте комментарий к этой теме, и я буду более чем счастлив ответить.

Особую благодарность следует также выразить Эстер Фалаи за его руководство, которое дало мне необходимое понимание Redux.

Полезные ссылки: