Урок 6: Настраиваем Store и подключаем его к React (`configureStore`, `Provider`)

В предыдущих уроках мы поняли, зачем вообще нужны менеджеры состояний, разобрались с фундаментальными концепциями Redux и даже написали свой первый «голый» редьюсер, чтобы почувствовать, как всё работает на низком уровне. Затем мы познакомились с нашим спасителем, с Redux Toolkit (RTK), который призван избавить нас от повторяющегося кода и создали свой первый элегантный слайс с помощью createSlice.

Но всё это было, если хотите, теорией и подготовкой. Наш слайс для счетчика пока что похож на мощный двигатель, который лежит на столе в гараже. Он полон потенциала, но сам по себе никуда не поедет. Пришло время установить этот двигатель в автомобиль, наше React-приложение. Этим мы и займемся сегодня.

В Уроке 6 мы сосредоточимся на двух ключевых задачах, создании хранилища (Store) с помощью функции configureStore из RTK и «прокидывании» этого хранилища во всё наше React-приложение с помощью компонента <Provider>. Звучит сложно? Уверяю вас, с Redux Toolkit это проще, чем кажется.

Создаем Store с помощью configureStore

В «ванильном» Redux создание хранилища было процедурой, требующей внимательности. Нужно было вручную подключать middleware (например для асинхронных действий) и отдельно настраивать интеграцию с Redux DevTools. Redux Toolkit кардинально меняет эту ситуацию.

Функция configureStore это стандартный, рекомендуемый способ создания хранилища в современных приложениях на Redux. Что же она делает такого волшебного? Во-первых, она по умолчанию подключает несколько полезных middleware, включая redux-thunk (для асинхронных операций, о которых мы поговорим позже). Это избавляет нас от необходимости делать это вручную. Во-вторых, она автоматически настраивает включение Redux DevTools Extension. Помните те самые инструменты разработчика, которые позволяют следить за каждым действием и «путешествовать во времени» по состоянию приложения? Теперь они будут работать из коробки, без лишних телодвижений с нашей стороны.

Но как же мы создаем это хранилище? Всё очень просто. Основной и зачастую единственный аргумент, который нас интересует в configureStore, это объект конфигурации. И ключевое свойство в этом объекте reducer. Именно здесь мы собираем все наши редьюсеры (или слайсы) в один корневой редьюсер. Давайте посмотрим на код. Представьте, что у нас есть проект, созданный с помощью Vite и мы уже установили зависимости @reduxjs/toolkit и react-redux.

Для начала создадим файл для нашего хранилища, например, app/store.js.

javascript
// app/store.js

// Импортируем функцию configureStore из RTK
import { configureStore } from '@reduxjs/toolkit';
// Импортируем наш редьюсер слайса счетчика
import counterReducer from '../features/counter/counterSlice';

// Создаем и экспортируем хранилище
export const store = configureStore({
  // Поле `reducer` ожидает объект, где ключи - это имена частей состояния,
  // а значения - редьюсеры, управляющие этими частями.
  reducer: {
    counter: counterReducer,
    // Сюда позже мы добавим редьюсер для задач (todos), пользователей (users) и т.д.
    // todos: todosReducer,
  },
});

Давайте разберем этот код по косточкам. Мы импортируем нашу функцию configureStore и редьюсер, который был сгенерирован для нас функцией createSlice в прошлом уроке (помните, counterSlice.reducer?). Обратите внимание, что мы даем этой части состояния понятное имя counter. Это имя будет ключом, по которому мы в дальнейшем будем обращаться к состоянию счетчика в нашем хранилище.

Наше хранилище создано. Оно уже сконфигурировано с лучшими практиками, имеет подключенные DevTools и готово к работе. Мы прошли первую и, возможно, самую простую часть пути. Но созданное хранилище пока что находится в изоляции. Наше приложение React о его существовании не знает. Пришло время познакомить их друг с другом.

В моем блоге вы также найдете полный цикл уроков по React для начинающих, где мы детально разбираем все ключевые концепции, от JSX и функциональных компонентов с хуками (useState, useEffect) до более сложных тем, таких как оптимизация рендеров и создание собственных хуков.

Оборачиваем приложение в <Provider>

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

Для этого в арсенале react-redux есть специальный компонент <Provider>. Его задача предоставить (provide) доступ к хранилищу всем компонентам приложения.

Как это работает на практике? Мы находим корневой компонент нашего приложения (обычно это компонент App или то, что рендерится в main.jsx / index.js) и оборачиваем его в компонент <Provider>. При этом мы передаем созданное нами хранилище в проп store. Давайте посмотрим, как это выглядит. Предположим, наш главный файл называется main.jsx.

javascript
// main.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
// Импортируем компонент Provider из react-redux
import { Provider } from 'react-redux';
// Импортируем наше созданное хранилище
import { store } from './app/store.js';
// Импортируем корневой компонент App
import App from './App.jsx';

// Рендерим наше приложение
ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    {/* Оборачиваем App в Provider и передаем ему store */}
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

Вот такой простой маневр и наше приложение теперь полностью «заряжено» Redux! Компонент <Provider> использует механизм Context API под капотом, чтобы незаметно для нас сделать объект хранилища доступным для любого компонента в дереве, где бы он ни находился. Больше не нужно вручную прокидывать пропсы через десятки компонентов, достаточно обернуть приложение один раз и всё.

Теперь любой компонент внутри <App /> (а значит и все компоненты) потенциально могут читать состояние из хранилища и отправлять действия для его изменения. Мощь этого подхода сложно переоценить. Мы только что установили фундамент для масштабируемого и предсказуемого управления состоянием.

От кода к работающему приложению

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

javascript
// features/counter/counterSlice.js

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

const initialState = {
  value: 0,
};

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    incremented: (state) => {
      // Redux Toolkit позволяет нам писать "мутирующую" логику в редьюсерах.
      // Это не мутирует состояние на самом деле, а использует библиотеку Immer,
      // которая обнаруживает изменения и возвращает новое неизменяемое состояние.
      state.value += 1;
    },
    decremented: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

// Автоматически генерируемые экшн-криэйторы
export const { incremented, decremented, incrementByAmount } = counterSlice.actions;

// Редьюсер, который должен быть подключен в хранилище
export default counterSlice.reducer;

Шаг 1: Создаем хранилище (store.js)

Создаем файл app/store.js и наполняем его кодом, который я показывал выше. Не забудьте правильно указать путь для импорта counterReducer.

Шаг 2: Подключаем хранилище к приложению (main.jsx)

Модифицируем точку входа нашего приложения, как было показано ранее, добавляя <Provider store={store}>.

Шаг 3: Проверяем, что всё работает (необязательный, но очень полезный шаг)

Откройте ваше приложение в браузере и откройте Redux DevTools. Если расширение установлено, вы должны увидеть новую вкладку «Redux» в инструментах разработчика. При открытии вы увидите начальное состояние нашего приложения: {counter: {value: 0}}. Это верный знак, что хранилище создано успешно и подключено к DevTools!

Вы только что совершили один из самых значимых шагов в изучении Redux. Вы создали централизованное хранилище, настроенное по лучшим практикам и подключили его к вашему React-приложению. Теперь у нас есть прочный фундамент. В следующем уроке мы научим наши React-компоненты общаться с этим хранилищем: читать состояние счетчика и dispatch-ить действия для его изменения с помощью хуков useSelector и useDispatch.

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

Практические задания для закрепления

  • Базовое подключение. Повторите все шаги урока в своем проекте. Убедитесь, что у вас создан слайс счетчика, хранилище и что <Provider> оборачивает ваше приложение. Проверьте работу через Redux DevTools.

  • Добавьте новый слайс. Чтобы почувствовать масштабируемость подхода, создайте еще один слайс, например, для управления статусом пользователя (userSlice). Пусть его начальное состояние содержит поле isLoggedIn: false и редьюсер toggleAuth, который меняет это значение на противоположное. Не забудьте добавить редьюсер этого слайса в ваш store под ключом auth. Проверьте в Redux DevTools, что начальное состояние теперь выглядит так: { counter: {...}, auth: {isLoggedIn: false} }.

  • Исследуйте DevTools. Откройте Redux DevTools и попробуйте сделать следующее:

    1. Найдите кнопку «Dispatch». Попробуйте задиспатчить действие вручную {type: 'counter/incremented'}. Значение счетчика в состоянии должно измениться на 1.

    2. Используйте кнопку «Jump» (>>) для навигации по действиям. Это и есть то самое «time-travel debugging».

На этом урок 6 завершен. Мы прошли точку невозврата и теперь наше приложение обрело мощную систему управления состоянием. В следующем уроке мы «оживим» наш интерфейс, заставив его взаимодействовать с хранилищем. До встречи.

Хочешь освоить Redux и Redux Toolkit полностью? Это был лишь один урок из моего большого и подробного курса, где мы шаг за шагом, от простого к сложному, разбираем всё, что нужно для уверенной работы с состоянием в React-приложениях.

Переходи к полному курсу по ссылке — курс с уроками по Redux и Redux Toolkit для начинающих

Поделиться статьей:
Поддержать автора блога

Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.

Персональные рекомендации
Оставить комментарий