Урок 5: Создаем первый слайс (Slice) с помощью `createSlice`

Приветствую на пятом уроке нашего большого курса управления состоянием с Redux и Redux Toolkit. Сегодня мы перейдем от теории к практической части, к созданию первого «слайса» (slice) с помощью функции createSlice из Redux Toolkit.

Если на предыдущих уроках мы вручную собирали наш редьюсер, как сложный механизм из множества винтиков (объявляли константы экшенов, писали action creators, следили за иммутабельностью в редьюсере), то сегодня мы познакомимся с инструментом, который делает эту работу за нас. Представь, что тебе подарили мощный фрезерный станок с ЧПУ вместо ножовки и напильника. Именно таким станком и является createSlice. Давай же разберемся, что это такое и как им пользоваться.

Что такое «Слайс» (Slice)?

В переводе с английского «slice» означает «ломтик» или «часть». Это очень точно отражает его суть.

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

Каждый слайс это самостоятельный модуль, который включает в себя:

  • Имя (name). Уникальный идентификатор, который будет префиксом для генерируемых экшенов.

  • Начальное состояние (initialState). Значение state по умолчанию для этой части приложения.

  • Редьюсеры (reducers). Объект, где ключ это название редьюсера, а значение функция, которая определяет, как состояние обновляется при отправке соответствующего экшена.

  • Автоматически сгенерированные экшены (actions). Для каждой функции в reducers createSlice автоматически создаст action creator с тем же именем.

Главная сутьcreateSlice в том, что она избавляет нас от написания тонны шаблонного кода. Нам больше не нужно:

  1. Вручную придумывать и объявлять константы для типов экшенов (например, const INCREMENT = 'counter/increment').

  2. Вручную писать функции-создатели экшенов (action creators), которые возвращают объект { type: INCREMENT, payload: ... }.

  3. Вручную собирать огромный корневой редьюсер через combineReducers (хотя для нескольких слайсов он все же понадобится, но RTK тоже упрощает этот процесс, о чем мы поговорим в следующем уроке).

createSlice делает все это сама, под капотом, следуя лучшим практикам Redux. Это не только ускоряет разработку, но и радикально снижает количество потенциальных ошибок, например, опечаток в типах экшенов.

Знакомимся с createSlice

Прежде чем мы напишем наш первый слайс, давай убедимся, что наш проект готов к работе. Как мы обсуждали в прошлом уроке, у тебя должен быть создан проект React (я использую Vite) с установленными зависимостями.

Если нужны уроки по React, то у меня есть отдельный курс.

Если ты еще этого не сделал, открой терминал и выполни команды:

bash
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 и начнем описание нашего слайса.

javascript
// 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. Это сердце нашего слайса. Это объект, где мы объявляем функции, которые будут обновлять состояние.

    1. increment и decrement. Самые простые редьюсеры. Они принимают только текущее state и изменяют его. Обрати внимание, что мы пишем state.value += 1. Это выглядит как мутация! Но под капотом Redux Toolkit использует библиотеку Immer, которая позволяет писать такой «мутабельный» код, а сама преобразует его в корректное иммутабельное обновление. Это огромное облегчение жизни и одна из ключевых фич RTK.

    2. incrementByAmount. Этот редьюсер принимает второй аргумент action. В action.payload лежат те данные, которые мы передали при вызове action creator. Если мы вызовем incrementByAmount(5), то в action.payload будет число 5.

  • Экспорты: В конце мы делаем два ключевых экспорта.

    • Action creators. counterSlice.actions это объект, содержащий все автоматически сгенерированные создатели экшенов. Мы деструктуризируем его и экспортируем incrementdecrementincrementByAmount и reset. Теперь в наших React-компонентах мы будем импортировать и использовать именно их для диспатча экшенов.

    • Редьюсер. counterSlice.reducer это и есть тот самый редьюсер, который мы передадим в наш store. Мы экспортируем его по умолчанию для удобства.

Практическое задание №1: Изучаем сгенерированные экшены

Давай на минутку отвлечемся от кода и посмотрим, что же именно сгенерировал для нас createSlice. Это поможет глубже понять механику.

Создай временный файл, например, testSlice.js и напиши в нем следующий код:

javascript
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 или просто посмотри на результат. Ты увидишь примерно следующее:

javascript
{ type: 'test/someAction', payload: 42 }

Функция someAction является action creator’ом. Когда мы вызываем ее с аргументом 42, она возвращает объект экшена с автоматически проставленным типом 'test/someAction' и переданными данными в поле payload.

Нам больше никогда не нужно вручную писать { type: 'COUNTER/INCREMENT' }createSlice делает это за нас, гарантируя уникальность и согласованность типов.

Практическое задание №2: Создаем слайс для списка задач (Todos)

Чтобы закрепить материал, давай создадим еще один слайс, но посложнее, для управления списком дел (todos). Это классика, которая покажет работу с массивами.

Создай файл src/store/todosSlice.js.

javascript
// 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 превращает наши «мутации» в чистое, иммутабельное обновление состояния.

Подведем итоги и посмотрим вперед

На этом уроке мы совершили огромный скачок вперед. Мы узнали:

  1. Что такое слайс и как он помогает организовать код по функциональным доменам.

  2. Как использовать волшебную функцию createSlice из Redux Toolkit для автоматического создания редьюсеров и экшенов.

  3. Что нам больше не нужно вручную прописывать типы экшенов и создавать action creators. За нас это делает RTK.

  4. Как благодаря Immer мы можем писать код, который выглядит как мутирующий, но на самом деле является иммутабельным.

У нас теперь есть два готовых, полностью функциональных слайса, counterSlice и todosSlice. Но пока они существуют сами по себе. На следующем уроке мы создадим хранилище (store), объединим в нем наши слайсы и подключим его к нашему React-приложению с помощью компонента <Provider>.

Это будет момент истины, когда наши труды оживут и мы сможем увидеть, как состояние обновляется в реальном интерфейсе.

Полный курс с уроками по Redux и Redux Toolkit для начинающих — https://max-gabov.ru/redux-dlya-nachinaushih.

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

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

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