Приветствую на пятом уроке нашего большого курса управления состоянием с Redux и Redux Toolkit. Сегодня мы перейдем от теории к практической части, к созданию первого «слайса» (slice) с помощью функции createSlice из Redux Toolkit.
Если на предыдущих уроках мы вручную собирали наш редьюсер, как сложный механизм из множества винтиков (объявляли константы экшенов, писали action creators, следили за иммутабельностью в редьюсере), то сегодня мы познакомимся с инструментом, который делает эту работу за нас. Представь, что тебе подарили мощный фрезерный станок с ЧПУ вместо ножовки и напильника. Именно таким станком и является createSlice. Давай же разберемся, что это такое и как им пользоваться.
Что такое «Слайс» (Slice)?
В переводе с английского «slice» означает «ломтик» или «часть». Это очень точно отражает его суть.
Слайс это часть общего состояния твоего приложения, которая содержит в себе логику для управления этой самой частью. Вместо того чтобы хранить один огромный редьюсер для всего состояния приложения, мы разбиваем его на логические модули, на слайсы. Например в интернет-магазине у тебя может быть слайс для управления корзиной товаров, слайс для данных пользователя, слайс для каталога продуктов и так далее.
Каждый слайс это самостоятельный модуль, который включает в себя:
-
Имя (name). Уникальный идентификатор, который будет префиксом для генерируемых экшенов.
-
Начальное состояние (initialState). Значение state по умолчанию для этой части приложения.
-
Редьюсеры (reducers). Объект, где ключ это название редьюсера, а значение функция, которая определяет, как состояние обновляется при отправке соответствующего экшена.
-
Автоматически сгенерированные экшены (actions). Для каждой функции в
reducerscreateSliceавтоматически создаст action creator с тем же именем.
Главная сутьcreateSlice в том, что она избавляет нас от написания тонны шаблонного кода. Нам больше не нужно:
-
Вручную придумывать и объявлять константы для типов экшенов (например,
const INCREMENT = 'counter/increment'). -
Вручную писать функции-создатели экшенов (action creators), которые возвращают объект
{ type: INCREMENT, payload: ... }. -
Вручную собирать огромный корневой редьюсер через
combineReducers(хотя для нескольких слайсов он все же понадобится, но RTK тоже упрощает этот процесс, о чем мы поговорим в следующем уроке).
createSlice делает все это сама, под капотом, следуя лучшим практикам Redux. Это не только ускоряет разработку, но и радикально снижает количество потенциальных ошибок, например, опечаток в типах экшенов.
Знакомимся с createSlice
Прежде чем мы напишем наш первый слайс, давай убедимся, что наш проект готов к работе. Как мы обсуждали в прошлом уроке, у тебя должен быть создан проект React (я использую Vite) с установленными зависимостями.
Если нужны уроки по React, то у меня есть отдельный курс.
Если ты еще этого не сделал, открой терминал и выполни команды:
npm create vite@latest my-redux-app -- --template react cd my-redux-app npm install npm install @reduxjs/toolkit react-redux
Теперь открой проект в любимом редакторе кода. Я для структуры создам папку src/store и внутри нее начну творить нашу логику. Это не обязательно, но помогает держать код организованным.
Создаем первый слайс
Лучший способ понять что-то новое, сделать это самому. Давай создадим классический пример, слайс для управления счетчиком. Он простой, но наглядно демонстрирует все ключевые концепции.
В папке src/store создай файл counterSlice.js.
Теперь импортируем функцию createSlice из Redux Toolkit и начнем описание нашего слайса.
// src/store/counterSlice.js import { createSlice } from '@reduxjs/toolkit'; // Определяем начальное состояние для нашего счетчика const initialState = { value: 0, }; // Создаем слайс export const counterSlice = createSlice({ // Уникальное имя слайса name: 'counter', // Начальное состояние initialState, // Объект "редьюсеров" reducers: { // Редьюсер для увеличения счетчика increment: (state) => { // Внутри редьюсера мы можем "мутабельно" обновлять состояние! // Благодаря Immer (который встроен в RTK) этот код преобразуется в // иммутабельное обновление. Это не прямой мутинг, а синтаксический сахар. state.value += 1; }, // Редьюсер для уменьшения счетчика decrement: (state) => { state.value -= 1; }, // Редьюсер для увеличения на определенное значение // Здесь мы используем `action` чтобы получить данные, переданные при диспатче incrementByAmount: (state, action) => { state.value += action.payload; }, // Редьюсер для сброса счетчика reset: (state) => { state.value = 0; }, }, }); // `createSlice` автоматически генерирует action creators для каждого редьюсера. // Названия action creators соответствуют названиям редьюсеров. // Мы можем их деструктуризировать и экспортировать. export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions; // Редьюсер, сгенерированный слайсом, должен быть добавлен в хранилище (store). // Экспортируем его по умолчанию. export default counterSlice.reducer;
Давай разберем этот код по косточкам, потому что здесь происходит много важного.
-
createSlice. Это главная функция, которая принимает объект с настройками. -
name: 'counter'. Это имя нашего слайса. Оно будет использоваться как префикс для типов экшенов. Например, для редьюсераincrementбудет сгенерирован тип экшена'counter/increment'. Это помогает избежать коллизий имен в больших приложениях. -
initialState. Мы задаем начальное состояние для этой части стора. В нашем случае это объект с одним полемvalue, равным 0. -
reducers. Это сердце нашего слайса. Это объект, где мы объявляем функции, которые будут обновлять состояние.-
incrementиdecrement. Самые простые редьюсеры. Они принимают только текущееstateи изменяют его. Обрати внимание, что мы пишемstate.value += 1. Это выглядит как мутация! Но под капотом Redux Toolkit использует библиотеку Immer, которая позволяет писать такой «мутабельный» код, а сама преобразует его в корректное иммутабельное обновление. Это огромное облегчение жизни и одна из ключевых фич RTK. -
incrementByAmount. Этот редьюсер принимает второй аргументaction. Вaction.payloadлежат те данные, которые мы передали при вызове action creator. Если мы вызовемincrementByAmount(5), то вaction.payloadбудет число 5.
-
-
Экспорты: В конце мы делаем два ключевых экспорта.
-
Action creators.
counterSlice.actionsэто объект, содержащий все автоматически сгенерированные создатели экшенов. Мы деструктуризируем его и экспортируемincrement,decrement,incrementByAmountиreset. Теперь в наших React-компонентах мы будем импортировать и использовать именно их для диспатча экшенов. -
Редьюсер.
counterSlice.reducerэто и есть тот самый редьюсер, который мы передадим в наш store. Мы экспортируем его по умолчанию для удобства.
-
Практическое задание №1: Изучаем сгенерированные экшены
Давай на минутку отвлечемся от кода и посмотрим, что же именно сгенерировал для нас createSlice. Это поможет глубже понять механику.
Создай временный файл, например, testSlice.js и напиши в нем следующий код:
import { createSlice } from '@reduxjs/toolkit'; const testSlice = createSlice({ name: 'test', initialState: { value: 0 }, reducers: { someAction: (state, action) => { state.value = action.payload; }, }, }); console.log(testSlice.actions.someAction(42));
Запусти этот скрипт в среде Node.js или просто посмотри на результат. Ты увидишь примерно следующее:
{ type: 'test/someAction', payload: 42 }
Функция someAction является action creator’ом. Когда мы вызываем ее с аргументом 42, она возвращает объект экшена с автоматически проставленным типом 'test/someAction' и переданными данными в поле payload.
Нам больше никогда не нужно вручную писать { type: 'COUNTER/INCREMENT' }. createSlice делает это за нас, гарантируя уникальность и согласованность типов.
Практическое задание №2: Создаем слайс для списка задач (Todos)
Чтобы закрепить материал, давай создадим еще один слайс, но посложнее, для управления списком дел (todos). Это классика, которая покажет работу с массивами.
Создай файл src/store/todosSlice.js.
// src/store/todosSlice.js import { createSlice } from '@reduxjs/toolkit'; // Начальное состояние - пустой массив задач const initialState = []; export const todosSlice = createSlice({ name: 'todos', initialState, reducers: { // Добавление новой задачи addTodo: (state, action) => { // Просто "пушим" новую задачу в массив. // Immer позаботится о иммутабельности. // Предполагаем, что action.payload - это строка с текстом задачи. state.push({ id: Date.now(), // Простой способ получить уникальный ID (в продакшене лучше использовать наноид или uuid) text: action.payload, completed: false, }); }, // Удаление задачи по ID removeTodo: (state, action) => { // Находим индекс задачи по ID и удаляем ее с помощью splice. // Фильтрация через filter также отлично работает, но splice здесь нагляднее. const index = state.findIndex(todo => todo.id === action.payload); if (index !== -1) { state.splice(index, 1); } }, // Переключение статуса выполнения задачи toggleTodo: (state, action) => { // Находим задачу по ID const todo = state.find(todo => todo.id === action.payload); if (todo) { // И "мутабельно" меняем ее свойство todo.completed = !todo.completed; } }, // Редактирование текста задачи editTodo: (state, action) => { // action.payload должен быть объектом { id, newText } const { id, newText } = action.payload; const todo = state.find(todo => todo.id === id); if (todo) { todo.text = newText; } }, }, }); // Экспортируем сгенерированные экшены export const { addTodo, removeTodo, toggleTodo, editTodo } = todosSlice.actions; // Экспортируем редьюсер export default todosSlice.reducer;
Здесь мы видим все те же принципы, но применяемые для работы с массивом. Обрати внимание, насколько лаконично и читаемо выглядит код. Мы просто работаем с state как с обычным массивом или объектом, а Immer превращает наши «мутации» в чистое, иммутабельное обновление состояния.
Подведем итоги и посмотрим вперед
На этом уроке мы совершили огромный скачок вперед. Мы узнали:
-
Что такое слайс и как он помогает организовать код по функциональным доменам.
-
Как использовать волшебную функцию
createSliceиз Redux Toolkit для автоматического создания редьюсеров и экшенов. -
Что нам больше не нужно вручную прописывать типы экшенов и создавать action creators. За нас это делает RTK.
-
Как благодаря Immer мы можем писать код, который выглядит как мутирующий, но на самом деле является иммутабельным.
У нас теперь есть два готовых, полностью функциональных слайса, counterSlice и todosSlice. Но пока они существуют сами по себе. На следующем уроке мы создадим хранилище (store), объединим в нем наши слайсы и подключим его к нашему React-приложению с помощью компонента <Provider>.
Это будет момент истины, когда наши труды оживут и мы сможем увидеть, как состояние обновляется в реальном интерфейсе.
Полный курс с уроками по Redux и Redux Toolkit для начинающих — https://max-gabov.ru/redux-dlya-nachinaushih.
Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.


