Как типизировать redux-стор с typescript правильно и безопасно для приложения

Зачем вообще типизировать Redux-стор?

Если вы работаете с Redux и TypeScript, скорее всего, уже сталкивались с болью от «any» или от непонятных ошибок в рантайме. Типизация состояния в Redux — это не просто приятное дополнение, а настоящая страховка от будущих багов. Особенно, когда приложение растёт, и экшены, редьюсеры и селекторы начинают жить своей жизнью.

Так вот, если хочется не просто «чтобы работало», а чтобы было удобно, безопасно и понятно другим разработчикам (и вам через полгода), давайте разберёмся, как грамотно и, главное, нестандартно типизировать Redux-стор с помощью TypeScript.

Базовая структура: откуда начать

Перед тем как прыгнуть в сложные решения, важно напомнить базу. В Redux обычно есть:

- actions (экшены)
- reducers (редьюсеры)
- store (стор)
- selectors (селекторы)
- middleware (если нужно)

Типизация Redux-стора TypeScript начинается с описания состояния (State) и экшенов (Actions). Но мы пойдём чуть дальше обычного `type RootState = ReturnType`.

Типизируем корневой редьюсер нестандартно

Вместо того чтобы вручную описывать `RootState`, можно создать утилиту, которая выводит тип состояния на основе объекта редьюсеров. Это особенно полезно, если вы используете `combineReducers`:

```ts
import { combineReducers } from '@reduxjs/toolkit';

const reducers = {
user: userReducer,
posts: postsReducer,
};

const rootReducer = combineReducers(reducers);

// Вот нестандартный способ:
type ExtractState = T extends { [K in keyof T]: infer R } ? {
[K in keyof T]: R extends (...args: any[]) => infer S ? S : never;
} : never;

type RootState = ExtractState;
```

Такой подход позволяет явно видеть, какие редьюсеры участвуют в сторе, и автоматически выводит их состояние. Это гибко и масштабируемо.

Типизация экшенов без боли

Как типизировать Redux-стор с помощью TypeScript - иллюстрация

Часто разработчики описывают экшены вручную, создавая enum'ы или строковые литералы. Это ок, но можно лучше.

Создаём фабрику типов экшенов

Вот идея: если вы используете action creators, можно типизировать их автоматически:

```ts
const createAction = (type: T) => {
return (payload: P) => ({ type, payload });
};

const setUser = createAction<'user/set', { name: string }>('user/set');

// Тип экшена:
type SetUserAction = ReturnType;
```

Теперь вы можете собрать тип всех экшенов через объединение:

```ts
type AppAction = SetUserAction | AnotherAction | WhateverAction;
```

Это удобно, особенно если actions хранятся в отдельных модулях. Не нужно вручную обновлять тип после каждого нового экшена — всё подтягивается через `ReturnType`.

Типизация useSelector и useDispatch

Как типизировать Redux-стор с помощью TypeScript - иллюстрация

Вот здесь чаще всего встречается копипаста. Но можно сделать по-другому.

Создаём собственные хуки

```ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';

export const useAppDispatch = () => useDispatch();
export const useAppSelector: TypedUseSelectorHook = useSelector;
```

И теперь в любом компоненте:

```ts
const user = useAppSelector(state => state.user);
const dispatch = useAppDispatch();
```

Никаких дублирующихся типов в компонентах. Всё типизировано, всё работает.

Хитрости: типизируем middleware и store

Если вы подключаете middleware вроде thunk или saga, важно не забыть их типизировать.

Пример для Thunk

```ts
import { ThunkAction, ThunkDispatch } from 'redux-thunk';

type AppThunk = ThunkAction< ReturnType, RootState, unknown, AppAction >;

type AppDispatch = ThunkDispatch;
```

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

Нестандартный приём: типизация slice'ов

Если вы используете Redux Toolkit, можно структурировать код чуть иначе. Вместо обычного `createSlice`, создаём свою обёртку, которая возвращает типизированный slice:

```ts
function createTypedSlice>(
options: CreateSliceOptions
) {
return createSlice(options);
}
```

Теперь можно создавать slice'ы с полной типизацией, не теряя в читаемости:

```ts
const userSlice = createTypedSlice({
name: 'user',
initialState: { name: '', age: 0 },
reducers: {
setName(state, action: PayloadAction) {
state.name = action.payload;
},
},
});
```

Это упрощает масштабирование: новые slice'ы добавляются с сохранением типизации без повторения шаблонного кода.

Несколько практических советов

Как типизировать Redux-стор с помощью TypeScript - иллюстрация

Чтобы типизация Redux-стора TypeScript не превратилась в ад, следуйте этим правилам:

- Не бойтесь создавать утилитные типы. Один раз написал — используешь везде.
- Избегайте дублирования. Всё, что можно вывести через `ReturnType` — выводите.
- Собирайте типы экшенов через `ReturnType`.
- Используйте Redux Toolkit. Он создан с учётом типизации и экономит кучу времени.

И напоследок

Типизация состояния в Redux — не про «показать, что я знаю TypeScript», а про удобство и уверенность в коде. Если всё сделано грамотно, вы не боитесь рефакторинга, не ломаете голову над типами в компонентах и не теряете время на отладку банальных ошибок.

Использование TypeScript с Redux — это не боль, если подходить к делу с умом. А если ещё и применить нестандартные решения, вроде генераторов типов или обёрток для хуков и slice'ов, можно получить удивительно приятный опыт.

Надеюсь, эти Redux TypeScript примеры вдохновят вас на более чистую и безопасную архитектуру.

Прокрутить вверх