← Назад к вопросам

Как работает CreateSlice?

2.3 Middle🔥 141 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает createSlice в Redux Toolkit

createSlice — функция из Redux Toolkit, которая упрощает создание Redux слайсов (reducers + actions). Это один из самых важных API современного Redux.

1. Что такое слайс и зачем он нужен

// Старый подход Redux (вручную создаём actions и reducers)
// actions.js
export const INCREMENT = "counter/INCREMENT";
export const DECREMENT = "counter/DECREMENT";

export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });

// reducers.js
const initialState = { value: 0 };

export function counterReducer(state = initialState, action) {
  switch(action.type) {
    case INCREMENT: return { ...state, value: state.value + 1 };
    case DECREMENT: return { ...state, value: state.value - 1 };
    default: return state;
  }
}

// Много кода для простой логики!
// Новый подход с createSlice (Redux Toolkit)
import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
  name: "counter",
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1; // Immer позволяет мутировать!
    },
    decrement: (state) => {
      state.value -= 1;
    }
  }
});

// Автоматически создаёт actions и reducer
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;

2. Структура createSlice

import { createSlice, PayloadAction } from "@reduxjs/toolkit";

interface CounterState {
  value: number;
  status: "idle" | "loading";
}

const initialState: CounterState = {
  value: 0,
  status: "idle"
};

const counterSlice = createSlice({
  // name: используется для генерации типов actions
  // например: "counter/increment", "counter/decrement"
  name: "counter",

  // initialState: начальное состояние
  initialState,

  // reducers: синхронные операции (не требуют async)
  // Принимают состояние (state) и payload (данные)
  reducers: {
    // state можно мутировать благодаря Immer
    // payload содержит данные из action
    increment: (state) => {
      state.value += 1;
    },

    decrement: (state) => {
      state.value -= 1;
    },

    // С параметром
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },

    // С объектом в payload
    setCounter: (state, action: PayloadAction<{ value: number; status: string }>) => {
      state.value = action.payload.value;
      state.status = action.payload.status as any;
    },

    // Обработка ошибок
    reset: (state) => {
      state.value = 0;
      state.status = "idle";
    }
  },

  // extraReducers: обработчики внешних actions (async thunks, другие slices)
  extraReducers: (builder) => {
    builder
      .addCase(fetchData.pending, (state) => {
        state.status = "loading";
      })
      .addCase(fetchData.fulfilled, (state, action) => {
        state.status = "idle";
        state.value = action.payload;
      })
      .addCase(fetchData.rejected, (state) => {
        state.status = "idle";
      });
  }
});

// Экспортируем actions
export const { increment, decrement, incrementByAmount, setCounter, reset } = counterSlice.actions;

// Экспортируем reducer
export default counterSlice.reducer;

3. Использование в компоненте

import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, incrementByAmount } from "./counterSlice";
import { RootState } from "./store";

function Counter() {
  // Получаем состояние из Redux
  const count = useSelector((state: RootState) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>

      {/* Вызов actions из slice */}
      <button onClick={() => dispatch(increment())}>
        Increment
      </button>

      <button onClick={() => dispatch(decrement())}>
        Decrement
      </button>

      {/* С параметром */}
      <button onClick={() => dispatch(incrementByAmount(5))}>
        Add 5
      </button>

      <button onClick={() => dispatch(setCounter({ value: 10, status: "idle" }))}>
        Set to 10
      </button>
    </div>
  );
}

export default Counter;

4. Immer и мутация состояния

// Redux Toolkit использует Immer
// Это позволяет "мутировать" состояние в reducers

const slice = createSlice({
  name: "todos",
  initialState: {
    items: [
      { id: 1, text: "Learn Redux", done: false }
    ]
  },
  reducers: {
    // Вариант 1: "Мутируем" напрямую (благодаря Immer)
    toggleTodo: (state, action: PayloadAction<number>) => {
      const todo = state.items.find(item => item.id === action.payload);
      if (todo) {
        todo.done = !todo.done; // Выглядит как мутация!
      }
    },

    // Вариант 2: Традиционный подход (return новое состояние)
    // Оба варианта работают одинаково
    addTodo: (state, action: PayloadAction<string>) => {
      state.items.push({
        id: Date.now(),
        text: action.payload,
        done: false
      });
    },

    // Вариант 3: Можем использовать стрелочный return
    // (Immer не используется в этом случае)
    removeTodo: (state, action: PayloadAction<number>) => {
      return {
        ...state,
        items: state.items.filter(item => item.id !== action.payload)
      };
    }
  }
});

export const { toggleTodo, addTodo, removeTodo } = slice.actions;

5. Асинхронные операции с createAsyncThunk

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

// createAsyncThunk автоматически создаёт 3 действия:
// - pending (запрос идёт)
// - fulfilled (успех)
// - rejected (ошибка)

export const fetchUsers = createAsyncThunk(
  "users/fetchUsers", // название action
  async (page: number) => {
    const response = await fetch(`/api/users?page=${page}`);
    return response.json();
  }
);

interface UsersState {
  items: any[];
  status: "idle" | "loading" | "succeeded" | "failed";
  error: string | null;
}

const usersSlice = createSlice({
  name: "users",
  initialState: {
    items: [],
    status: "idle",
    error: null
  } as UsersState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      // Запрос начался
      .addCase(fetchUsers.pending, (state) => {
        state.status = "loading";
      })
      // Запрос успешен
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.status = "succeeded";
        state.items = action.payload;
      })
      // Запрос ошибка
      .addCase(fetchUsers.rejected, (state, action) => {
        state.status = "failed";
        state.error = action.error.message || "Unknown error";
      });
  }
});

export default usersSlice.reducer;

6. Конфигурация Store

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";
import usersReducer from "./usersSlice";
import todoReducer from "./todoSlice";

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    users: usersReducer,
    todo: todoReducer
  }
});

// Типизация
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

7. Полный пример с features

// features/counter/counterSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";

interface CounterState {
  value: number;
  history: number[];
}

const initialState: CounterState = {
  value: 0,
  history: [0]
};

export const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
      state.history.push(state.value);
    },
    decrement: (state) => {
      state.value -= 1;
      state.history.push(state.value);
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
      state.history.push(state.value);
    },
    undo: (state) => {
      if (state.history.length > 1) {
        state.history.pop();
        state.value = state.history[state.history.length - 1];
      }
    }
  }
});

export const { increment, decrement, incrementByAmount, undo } = counterSlice.actions;
export default counterSlice.reducer;

// hooks/useCounter.ts
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, incrementByAmount, undo } from "../counterSlice";
import { RootState } from "../store";

export function useCounter() {
  const value = useSelector((state: RootState) => state.counter.value);
  const dispatch = useDispatch();

  return {
    value,
    increment: () => dispatch(increment()),
    decrement: () => dispatch(decrement()),
    incrementByAmount: (amount: number) => dispatch(incrementByAmount(amount)),
    undo: () => dispatch(undo())
  };
}

// Counter.tsx
import { useCounter } from "./hooks/useCounter";

export function Counter() {
  const { value, increment, decrement, incrementByAmount, undo } = useCounter();

  return (
    <div>
      <p>Value: {value}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
      <button onClick={() => incrementByAmount(5)}>+5</button>
      <button onClick={undo}>Undo</button>
    </div>
  );
}

Рекомендация

createSlice упрощает Redux в 10 раз:

  1. Меньше кода — не нужно писать action creators вручную
  2. Immer — мутируй состояние безопасно
  3. Типизация — PayloadAction обеспечивает полную типизацию
  4. extraReducers — обработка async операций
  5. Организация — разделяй по features (counter, users, todo)