Ручная интеграция с React JS: Redux (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);

// Создаём действие и оборачиваем его в функцию
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 передаст внутрь контейнера:

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

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

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

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


<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

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

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

Получить доступ
120
курсов
900
упражнения
2000+
часов теории
3200
тестов

Открыть доступ

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

  • 120 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».

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

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

Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия

Фронтенд-разработчик

Разработка фронтенд-компонентов веб-приложений
23 июня 8 месяцев

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

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

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».