Как сделать handleActions на TypeScript в Redux

Короткий ответ: handleActions обычно относится к библиотеке redux-actions, а не к современному Redux Toolkit. Если проект legacy и handleActions уже используется, типизируйте состояние, payload и action creator явно. Если проект новый, лучше рассмотреть createSlice из Redux Toolkit: там TypeScript-поддержка проще и надежнее

Ниже пример именно для случая, когда handleActions уже есть в проекте

Тип состояния

type CounterState = {
  value: number;
  loading: boolean;
};

const initialState: CounterState = {
  value: 0,
  loading: false,
};

Тип payload

type IncrementPayload = {
  amount: number;
};

Если action несет данные, не оставляйте payload как any. Именно payload чаще всего ломает типизацию reducer

Action creator

В legacy-коде может быть так:

import { createAction } from "redux-actions";

export const increment = createAction<IncrementPayload>("counter/increment");

Использование:

dispatch(increment({ amount: 2 }));

TypeScript должен ругаться, если передать { amount: "2" }

Reducer через handleActions

import { handleActions, Action } from "redux-actions";

export const counterReducer = handleActions<CounterState, IncrementPayload>(
  {
    [increment.toString()]: (
      state,
      action: Action<IncrementPayload>
    ): CounterState => {
      return {
        ...state,
        value: state.value + (action.payload?.amount ?? 0),
      };
    },
  },
  initialState
);

Здесь явно указаны:

  • тип состояния CounterState;
  • тип payload IncrementPayload;
  • тип action Action<IncrementPayload>;
  • возвращаемый тип reducer.

Почему payload может быть optional

В некоторых типах redux-actions payload может считаться необязательным. Поэтому в примере используется:

action.payload?.amount ?? 0

Если по бизнес-логике payload обязателен, можно добавить проверку и выбросить ошибку:

if (!action.payload) {
  return state;
}

Как типизировать несколько action

Если reducer обрабатывает несколько действий, для каждого payload лучше завести отдельный тип:

type SetLoadingPayload = {
  loading: boolean;
};

export const setLoading = createAction<SetLoadingPayload>("counter/setLoading");
[setLoading.toString()]: (
  state,
  action: Action<SetLoadingPayload>
): CounterState => ({
  ...state,
  loading: action.payload?.loading ?? state.loading,
})

Не пытайтесь сделать один общий payload “на все случаи”. Так типы быстро станут расплывчатыми, а reducer начнет принимать поля, которые конкретному action не нужны

Современная альтернатива через Redux Toolkit

Для нового кода проще:

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

type CounterState = {
  value: number;
};

const initialState: CounterState = { value: 0 };

const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment(state, action: PayloadAction<{ amount: number }>) {
      state.value += action.payload.amount;
    },
  },
});

Redux Toolkit сейчас является рекомендуемым путем для обычных Redux-проектов. handleActions стоит поддерживать, если он уже есть в кодовой базе

Частые ошибки

Первая ошибка — типизировать только state, но оставить action как any. Тогда TypeScript не проверяет payload

Вторая ошибка — использовать строку action type вручную в нескольких местах. Лучше брать increment.toString() или единый constant

Третья ошибка — смешивать старый redux-actions и Redux Toolkit без причины. Выберите стиль для конкретного модуля

Четвертая ошибка — не проверять optional payload, если тип библиотеки допускает его отсутствие

Пятая ошибка — продолжать наращивать legacy-слой, когда модуль уже можно спокойно переписать на createSlice. Для старого проекта это не всегда срочно, но для нового кода лучше не начинать с handleActions

Самопроверка

Создайте action increment({ amount: 2 }), reducer через handleActions и dispatch с неправильным payload { amount: "2" }. TypeScript должен поймать ошибку. Затем перепишите тот же пример на createSlice и сравните объем кода

Что почитать дальше по TypeScript

Если нужен общий маршрут по теме, откройте рубрику TypeScript. Для соседних задач пригодятся эти разборы:

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x