В предыдущих уроках мы поняли, зачем вообще нужны менеджеры состояний, разобрались с фундаментальными концепциями 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.
// 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.
// 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. Напомню, как он мог выглядеть:
// 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 и попробуйте сделать следующее:
-
Найдите кнопку «Dispatch». Попробуйте задиспатчить действие вручную
{type: 'counter/incremented'}. Значение счетчика в состоянии должно измениться на 1. -
Используйте кнопку «Jump» (>>) для навигации по действиям. Это и есть то самое «time-travel debugging».
-
На этом урок 6 завершен. Мы прошли точку невозврата и теперь наше приложение обрело мощную систему управления состоянием. В следующем уроке мы «оживим» наш интерфейс, заставив его взаимодействовать с хранилищем. До встречи.
Хочешь освоить Redux и Redux Toolkit полностью? Это был лишь один урок из моего большого и подробного курса, где мы шаг за шагом, от простого к сложному, разбираем всё, что нужно для уверенной работы с состоянием в React-приложениях.
Переходи к полному курсу по ссылке — курс с уроками по Redux и Redux Toolkit для начинающих
Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.


