Зачем вообще типизировать 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
[K in keyof T]: R extends (...args: any[]) => infer S ? S : never;
} : never;
type RootState = ExtractState
```
Такой подход позволяет явно видеть, какие редьюсеры участвуют в сторе, и автоматически выводит их состояние. Это гибко и масштабируемо.
Типизация экшенов без боли

Часто разработчики описывают экшены вручную, создавая enum'ы или строковые литералы. Это ок, но можно лучше.
Создаём фабрику типов экшенов
Вот идея: если вы используете action creators, можно типизировать их автоматически:
```ts
const createAction =
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

Вот здесь чаще всего встречается копипаста. Но можно сделать по-другому.
Создаём собственные хуки
```ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
export const useAppDispatch = () => useDispatch
export const useAppSelector: TypedUseSelectorHook
```
И теперь в любом компоненте:
```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
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 не превратилась в ад, следуйте этим правилам:
- Не бойтесь создавать утилитные типы. Один раз написал — используешь везде.
- Избегайте дублирования. Всё, что можно вывести через `ReturnType` — выводите.
- Собирайте типы экшенов через `ReturnType
- Используйте Redux Toolkit. Он создан с учётом типизации и экономит кучу времени.
И напоследок
Типизация состояния в Redux — не про «показать, что я знаю TypeScript», а про удобство и уверенность в коде. Если всё сделано грамотно, вы не боитесь рефакторинга, не ломаете голову над типами в компонентах и не теряете время на отладку банальных ошибок.
Использование TypeScript с Redux — это не боль, если подходить к делу с умом. А если ещё и применить нестандартные решения, вроде генераторов типов или обёрток для хуков и slice'ов, можно получить удивительно приятный опыт.
Надеюсь, эти Redux TypeScript примеры вдохновят вас на более чистую и безопасную архитектуру.



