Типизация состояния
Состояние (state) является основным "хранилищем" данных в Redux. Типизация состояния позволяет точно знать, какие данные находятся в вашем хранилище. Для начала создадим интерфейс, описывающий структуру состояния.
Предположим, у нас есть состояние, отвечающее за данные пользователя и статус загрузки:
// types/user.ts
export interface User {
id: string;
name: string;
email: string;
}
export interface UserState {
currentUser: User | null; // Пользователь может быть либо объектом, либо null
isLoading: boolean; // Индикатор загрузки
error: string | null; // Сообщение об ошибке, если есть
}
Теперь вместо магического объекта state в срезах и редьюсерах, у нас появляется четко определенная структура.
Типизация среза (slice)
С помощью createSlice мы можем легко управлять состоянием и действиями. Важно, чтобы состояние, которое мы определили ранее, было явно типизировано.
Вот пример создания среза с типизированным состоянием:
// store/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { UserState, User } from '../types/user';
const initialState: UserState = {
currentUser: null,
isLoading: false,
error: null,
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
startLoading(state) {
state.isLoading = true;
state.error = null;
},
setUser(state, action: PayloadAction<User>) {
state.currentUser = action.payload; // Тип User проверяется автоматически
state.isLoading = false;
},
setError(state, action: PayloadAction<string>) {
state.error = action.payload; // Убедимся, что ошибка — это строка
state.isLoading = false;
},
},
});
export const { startLoading, setUser, setError } = userSlice.actions;
export default userSlice.reducer;
Что здесь произошло?
- Мы явно указали тип для состояния
UserState. - Использовали
PayloadAction<T>для типизации полезной нагрузкиpayloadв действиях. - TypeScript гарантирует, что тип данных при вызове
setUserилиsetErrorбудет соответствовать ожидаемому.
Типизация действий (actions)
Действия (actions) в Redux определяют изменения, которые вы хотите внести в состояние. Типизация действует на двух уровнях:
- Тип полезной нагрузки
payloadдля каждого действия. - Тип всех возможных действий в целой системе.
Мы уже видели, как используется PayloadAction<T> для типизации полезной нагрузки. Давайте создадим пример вызова действия в компоненте:
// components/UserComponent.tsx
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../store/store';
import { startLoading, setUser, setError } from '../store/userSlice';
const UserComponent: React.FC = () => {
const user = useSelector((state: RootState) => state.user.currentUser);
const dispatch = useDispatch();
useEffect(() => {
dispatch(startLoading());
// Имитация API-запроса
setTimeout(() => {
const fetchedUser = { id: '1', name: 'John Doe', email: 'john@example.com' };
if (fetchedUser) {
dispatch(setUser(fetchedUser)); // TypeScript гарантирует, что объект соответствует типу User
} else {
dispatch(setError('User not found'));
}
}, 1000);
}, [dispatch]);
if (!user) {
return <p>Loading...</p>;
}
return (
<div>
<h1>Welcome, {user.name}!</h1>
</div>
);
};
export default UserComponent
Теперь TypeScript заботится, чтобы мы случайно не передали в setUser что-то вроде объекта { value: 42 }. Если попробуем, компилятор обидится.
Работа с корневым состоянием (RootState)
В крупных приложениях у нас будет несколько срезов для разных частей состояния. Чтобы объединить их, используется объект RootState.
// store/store.ts
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
export const store = configureStore({
reducer: {
user: userReducer, // Можно добавить больше редьюсеров
},
});
// Тип для корневого состояния
export type RootState = ReturnType<typeof store.getState>;
// Тип для dispatch (с учетом асинхронных действий)
export type AppDispatch = typeof store.dispatch;
Теперь мы можем использовать RootState в хуке useSelector, чтобы точно знать, какие части состояния доступны.
Общая типизация состояния и действий
В Redux Toolkit работать с типизацией стало намного проще. Основные принципы:
- Всегда описывайте интерфейсы для состояния и полезной нагрузки действий.
- Используйте встроенные типы
PayloadActionиReturnType, чтобы избежать дублирования кода. - Типизируйте
RootStateиAppDispatchдля глобального использования.
В нашем примере всё вместе выглядит следующим образом:
- Типизированный интерфейс состояния
UserState. - Типизированные действия внутри среза.
- Типизированное корневое состояние
RootState. - Типизированные хуки
useSelector,useDispatch.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ