Ручная интеграция с React

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

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

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

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, его задача - хранить данные и модифицировать их. Эта мысль очень важна, её нужно прочувствовать. Воспринимайте контейнер как базу данных.

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

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

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 = () => {
    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 Increment from './components/Increment.jsx';
// Импортируем редьюсеры
import reducers from './reducers.jsx';

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

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

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

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


// Первый раз нужно отрисовать руками
ReactDOM.render(
  <Increment 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;

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

Для полного доступа к курсу, нужна профессиональная подписка

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

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».