Что такое slice в Redux Toolkit
Добро пожаловать на лекцию, посвящённую одному из самых эффективных способов управления состоянием в React-приложениях — Redux Toolkit! Сегодня мы поговорим о срезах (slices), чтобы понять, как они упрощают и структурируют наш код. Готовы? Погнали!
На самом деле, createSlice — это главный герой сегодняшней лекции и настоящая магия Redux Toolkit. Slice — это более элегантный способ описать логику изменения состояния и действия (актионы) в одном месте. Раньше мы объявляли экшены, редьюсеры, типы действий и связывали их вручную. Это было сложно, муторно и часто конфузило разработчиков. Но Redux Toolkit говорит: "Хватит!". Теперь всё находится в одном месте.
Преимущества использования slice:
- Меньше boilerplate-кода — нет необходимости громоздить несколько файлов для экшенов и редьюсеров.
- Инкапсуляция логики — все состояния и действия описаны в одном месте.
- Лучший DX (Developer Experience) — меньше боли, больше удовольствия.
Slice можно представить как небольшую коробочку с "темой" вашего приложения. Например, у нас есть приложение для списка задач (todo-list). Мы можем создать todoSlice, который будет управлять состоянием задач: их добавлением, удалением и обновлением. Другой пример — authSlice для управления состоянием аутентификации, например, логином и токенами.
Создание первого slice
Давайте сразу перейдём к практике. Мы настроим slice для простого Todo-приложения. В этом приложении можно будет добавлять задачи, удалять их и изменять статус выполнения.
Начнём с установки Redux Toolkit. Если вы ещё не установили Redux Toolkit, запустите следующую команду:
npm install @reduxjs/toolkit react-redux
Эта команда добавляет Redux Toolkit и react-redux, чтобы связать Redux с React.
Создание файла todoSlice.ts
Сначала создадим файл, где и будет описана логика нашего Todo-приложения. Пусть он лежит в папке store/slices. Структура проекта может выглядеть так:
src/
store/
slices/
todoSlice.ts
Теперь напишем наш todoSlice. Вот что нужно сделать.
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
// Определяем интерфейс для состояния задач
interface Todo {
id: string;
text: string;
completed: boolean;
}
// Тип для начального состояния
interface TodoState {
todos: Todo[];
}
// Начальное состояние
const initialState: TodoState = {
todos: [],
};
// Создаём slice
const todoSlice = createSlice({
name: "todos", // Название slice
initialState, // Начальное состояние
reducers: {
// Добавляем задачу
addTodo: (state, action: PayloadAction<string>) => {
const newTodo: Todo = {
id: crypto.randomUUID(),
text: action.payload,
completed: false,
};
state.todos.push(newTodo);
},
// Удаляем задачу
removeTodo: (state, action: PayloadAction<string>) => {
state.todos = state.todos.filter((todo) => todo.id !== action.payload);
},
// Переключение статуса задачи
toggleTodo: (state, action: PayloadAction<string>) => {
const todo = state.todos.find((todo) => todo.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
},
});
// Экспортируем действия
export const { addTodo, removeTodo, toggleTodo } = todoSlice.actions;
// Экспортируем редьюсер для добавления в Store
export default todoSlice.reducer;
Пояснения по коду
createSlice:name: название среза, используется для определения пространства имён.initialState: начальное состояние для среза.reducers: это ваши редьюсеры. Они описывают, как изменяется состояние при выполнении определённых действий.
Типизация:
- Мы используем
PayloadAction<T>для определения типа данных, передаваемых в экшен.
- Мы используем
Редьюсеры
addTodo,removeTodo,toggleTodo:- Они модифицируют массив задач в зависимости от действия. Обратите внимание, что в Redux Toolkit можно мутировать
stateблагодаря использованию Immer.
- Они модифицируют массив задач в зависимости от действия. Обратите внимание, что в Redux Toolkit можно мутировать
Интеграция с Store
Теперь, когда slice готов, нам нужно подключить его к Redux Store, чтобы с ним можно было работать из компонентов.
Файл store.ts
Создайте файл store.ts в папке store. В нём мы будем объединять все срезы в едином Store.
import { configureStore } from "@reduxjs/toolkit";
import todosReducer from "./slices/todoSlice";
export const store = configureStore({
reducer: {
todos: todosReducer, // Подключаем наш slice
},
});
// Типизация для хуков useSelector и useDispatch
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Связываем Store с React
Теперь нужно подключить наш Store к React-приложению. Для этого в файле index.tsx обернём наше приложение в Provider.
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { store } from "./store";
import App from "./App";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Работа с Redux в React-компонентах
Теперь пришло время проверить работу нашего среза на практике.
Пример компонента для отображения и управления задачами
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { RootState, AppDispatch } from "../store";
import { addTodo, removeTodo, toggleTodo } from "../store/slices/todoSlice";
const TodoApp: React.FC = () => {
const [task, setTask] = useState("");
const todos = useSelector((state: RootState) => state.todos.todos);
const dispatch: AppDispatch = useDispatch();
const handleAddTask = () => {
if (task.trim() !== "") {
dispatch(addTodo(task));
setTask("");
}
};
return (
<div>
<h1vTodo List</h1>
<input
type="text"
value={task}
onChange={(e) => setTask(e.target.value)}
placeholder="Enter a task"
/>
<button onClick={handleAddTask}>Add Task</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
onClick={() => dispatch(toggleTodo(todo.id))}
>
{todo.text}
</span>
<button onClick={() => dispatch(removeTodo(todo.id))}>Delete</button>
</li>
))}
</ul>
</div>
);
};
export default TodoApp;
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ