Зарегистрируйтесь, чтобы продолжить обучение

Подключение Redux Toolkit к React React: Redux Toolkit

Перейдем к Redux Toolkit и соберем простое приложение с несколькими кнопками — они будут менять значение счетчика. На этом примере мы увидим основные концепции Redux Toolkit. Для интеграции нам понадобятся два пакета:

  • Пакет react-redux
  • Сам Redux Toolkit из пакета @reduxjs/toolkit

Перейдем к установке:

# Выполните эту команду в корневой директории проекта
npm install @reduxjs/toolkit react-redux

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

components/
  | App.jsx
slices/
  | index.js
  | counterSlice.js
index.jsx

Для работы с Toolkit мы выделили директорию slices с двумя файлами внутри — index.js и counterSlice.js.

В файле index.js мы будем инициализировать хранилище и объединять все редьюсеры. Сами редьюсеры разбиваются по отдельным файлам — слайсам или срезам. Каждый слайс отвечает за определенную сущность в приложении. Например, в приложении со списком товаров и покупателей можно выделить два слайса: один для товаров, другой для покупателей.

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

// file: slices/counterSlice.js

import { createSlice } from '@reduxjs/toolkit';

// Начальное значение
const initialState = {
  value: 0,
};

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  // Редьюсеры в слайсах меняют состояние и ничего не возвращают
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    // Пример с данными
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

// Слайс генерирует действия, которые экспортируются отдельно
// Действия генерируются автоматически из имен ключей редьюсеров
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// По умолчанию экспортируется редьюсер, сгенерированный слайсом
export default counterSlice.reducer;

Чтобы создать редьюсер, создаем начальное значение initialState и вызываем createSlice(), который выполняет всю работу. Эта функция принимает объект, в котором нам важны три свойства:

  • name задает имя слайса
  • initialState задает начальное состояние
  • reducers принимает объект, в котором каждое свойство содержит редьюсеры. С их помощью мы будем менять состояние

Вызов createSlice() вернет готовый слайс — это объект, в котором нам важны два свойства:

  • actions — это действия, с помощью которых мы запускаем созданные редьюсеры. Названия действий совпадают с ключами, которые мы указали в reducers при создании слайса. Toolkit автоматически создаст нужные действия и даст строковые имена их типам.

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

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

Теперь редьюсер готов к использованию. Сначала подключим его в общее хранилище. Для этого передаем редьюсер в функцию configureStore().

Эта функция умеет комбинировать редьюсеры самостоятельно, в отличие от такой же функции в Redux. Функция configureStore() принимает на вход объект с ключом reducer, значением которого становится объект с редьюсерами. У общего состояния state ключи будут такими же, как у этого объекта. Более подробно мы это разберем чуть позже. Создание хранилища выглядит так:

// file: slices/index.js

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice.js';

export default configureStore({
  reducer: {
    // Свойство counter будет внутри объекта общего состояния: state.counter
    counter: counterReducer,
  },
});

Здесь мы вызываем функцию configureStore() и передаем в нее объект со свойством reducer. А вот уже в reducer мы указываем объект с нашими редьюсерами. В нашем примере есть единственный редьюсер counterReducer, который мы импортируем по умолчанию из counterSlice.js.

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

// file: slices/index.js

import { configureStore } from '@reduxjs/toolkit';
import usersReducer from './usersReducer.js';
import tasksReducer from './tasksReducer.js';

export default configureStore({
  reducer: {
    users: usersReducer,
    tasks: tasksReducer,
  },
});

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

Теперь наше хранилище готово к использованию. Можно подключить его в приложение.

Начнем создавать приложение с верхнего уровня. Здесь понадобится компонент <Provider>, который содержит хранилище и прокидывает его вглубь дерева компонентов через контекст:

// file: index.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';

import App from './components/App.jsx';
import store from './slices/index.js';

const mountNode = document.getElementById('container');
const root = ReactDOM.createRoot(mountNode);
// Оборачиваем приложение в Provider и передаем хранилище в него
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

Теперь самое главное — Toolkit в действии. Здесь мы опираемся на работу хуков. Здесь все работает по-прежнему:

  • Чтобы изменить состояние в хранилище, передаем действие в функцию dispatch()
  • Чтобы получить объект dispatch в компоненте, используем функцию useDispatch()
  • Чтобы извлечь данные из стора, используем хук useSelector(). Он принимает функцию, в которую все состояние передается через параметр. Возвращаемое значение из этой функции станет результатом выполнения useSelector()

Посмотрим, как это работает:

// file: components/App.jsx

import React from 'react';
// Хуки находятся в react-redux
import { useSelector, useDispatch } from 'react-redux';
// Импортируем нужные действия
import { decrement, increment, incrementByAmount } from '../slices/counterSlice.js';

export default () => {
  // Вытаскиваем данные из хранилища
  // Здесь state — это все состояние
  const count = useSelector((state) => state.counter.value);
  // Возвращает метод store.dispatch() текущего хранилища
  const dispatch = useDispatch();

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Прибавить
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Отнять
        </button>
        <br />
        <button onClick={() => dispatch(incrementByAmount(42))}>Прибавить 42</button>
      </div>
    </div>
  );
};

В компоненте может быть несколько вызовов useSelector, причем каждый вызов создаст подписку на изменение состояния. Срабатывание нескольких подписок одновременно приведет только к одной перерисовке компонента.

Обратите внимание, что переданная в useSelector функция принимает все состояние целиком. Если у нас несколько редьюсеров и слайсов, состояние содержит все состояния этих слайсов. Состояние хранится в объекте, где каждый ключ — это то, что мы указали в reducer при создании стора. В нашем случае это свойство counter:

export default configureStore({
  reducer: {
    // Свойство `counter` будет внутри объекта общего состояния `state.counter`
    counter: counterReducer,
  },
});

Рассмотрим интерактивный пример:

See the Pen js_redux_toolkit_integration-1 by Hexlet (@hexlet) on CodePen.

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

Осталось разобрать подключение мидлвар:


const logger = (store) => (next) => (action) => {
  // ...
};

const api = (store) => (next) => (action) => {
  // ...
};

export const store = configureStore({
  reducer: {
    // ...
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat([logger, api]),
})

Мидлвары подключаются через свойство middleware. В это свойство мы записываем функцию, которая в свою очередь принимает другую функцию getDefaultMiddleware(). Вызвав ее, мы получаем список текущих мидлвар. К этим мидлварам добавляем наши мидлвары и возвращаем новый список.


Самостоятельная работа

  1. Создайте репозиторий
  2. Откройте репозиторий локально и повторите в нем шаги из урока
  3. Зафиксируйте изменения в репозитории

Дополнительные материалы

  1. Redux DevTools
  2. create-react-app

Аватары экспертов Хекслета

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

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

Для полного доступа к курсу нужен базовый план

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

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

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

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Верстка на HTML5 и CSS3, Программирование на JavaScript в браузере, разработка клиентских приложений используя React
10 месяцев
с нуля
Старт 26 декабря
профессия
Программирование на JavaScript в браузере и на сервере (Node.js), разработка бекендов на Fastify и фронтенда на React
16 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

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

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»