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

  1. Возникает событие. Например, пользователь кликнул по кнопке.
  2. Обработчик события выполняет какую-то логику и, в конце, обновляет контейнер через store.dispatch.
  3. Контейнер по очереди вызывает все функции добавленные, через store.subscribe. Эти функции меняют представление на основе нового состояния внутри контейнера. И так по кругу. Событие -> Изменение состояния -> Отрисовка нового состояния.

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

import { createStore } from 'redux';

const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    default:
      return state;
  }
};

const store = createStore(reducer);

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

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

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

import React from 'react';

export default class Increment extends React.Component {
  static defaultProps = {
    count: 0,
  };

  render() {
    const { count } = this.props;
    return (
      <div>
        <button>{count}</button>
      </div>
    )
  }
}

Компонент Increment работает со свойством count. Его имя выбрано произвольно, нам не нужно опираться на структуру контейнера.

Теперь добавим обработчики. Напомню, что каждый обработчик в конце своей работы должен обновить состояние контейнера. С технической точки зрения произойдёт вызов функции store.dispatch и нужного действия. Откуда нам их взять внутри компонента? Все просто, мы их прокинем как свойства в наш компонент.

import React from 'react';

export default class Increment extends React.Component {
  handleClick = (e) => {
    const { dispatch, increment } = this.props;
    dispatch(increment());
  }

  render() {
    const { count } = this.props;
    return (
      <div>
        <button onClick={this.handleClick}>{count}</button>
      </div>
    )
  }
}

Остался последний шаг, нужно вызывать перерисовку компонента после изменения содержимого контейнера. В этом нам поможет функция store.subscribe:

import ReactDOM from 'react-dom';
import React from 'react';
import { createStore } from 'redux';

// Импортируем компонент
import App from './components/App';
// Импортируем редьюсеры
import reducers from './reducers';

// Создаем контейнер. Редьюсеры описаны в отдельном файле
const store = createStore(reducers);

// Создаем Action и оборачиваем его в функцию
export const increment = () => ({
  type: 'INCREMENT',
  payload: {},
});

// Элемент для подключения реакта
const containerElement = document.getElementById('container'),

// Подписываемся на изменения состояния внутри контейнера
// На каждое изменение отрисовываем наш компонент заново
store.subscribe(() => {
  const state = store.getState();
  ReactDOM.render(
    <App dispatch={store.dispatch} count={state} increment={increment} />,
    containerElement,
  );
});


// Первый раз нужно отрисовать руками
ReactDOM.render(
  <App dispatch={store.dispatch} increment={increment} />,
  containerElement,
);

Когда все необходимые объекты созданы, происходит первоначальная отрисовка компонента в DOM. В компонент передаются необходимые данные, в нашем случае функция store.dispatch и функция increment. Последняя создает действие при своем вызове. Дальше начинает работать последовательность шагов, описанная в начале урока:

  1. Пользователь нажимает на кнопку
  2. Срабатывает обработчик handleClick, который вызывает dispatch(increment()).
  3. Выполняется редьюсер и его ветка INCREMENT. Она увеличивает счетчик на единицу.
  4. Контейнер вызывает функции добавленные через subscribe. В нашем случае это одна функция.
  5. Эта функция извлекает состояние из контейнера через функцию store.getState.
  6. Затем эта же функция перерисовывает компонент в DOM передавая ей новое состояние.

На каждом этапе этого процесса можно вносить различные изменения. Например нам может понадобится передавать несколько функций создающих действия. Достаточно просто их передать. Некоторые из этих функций могут принимать данные, которые store.dispatch передаст внутрь контейнера:

export const increment = (step = 1) => ({
  type: 'INCREMENT',
  payload: { step },
});

Такой инкремент позволяет менять шаг приращения. Внутри контейнера код поменяется на такой:

case 'INCREMENT':
  return state + action.payload.step;

Само состояние внутри контейнера может стать структурой, например объектом.

Для продолжения нужно перейти в курс и вступить в него.