Базовая структура Thunk
Прежде чем углубимся в типизацию, давайте вспомним, как выглядит базовый Thunk:
// Простой Thunk без типизации
export const fetchUserData = () => {
return async (dispatch: any) => {
const response = await fetch(`/api/users`);
const data = await response.json();
dispatch({ type: "SET_USER_DATA", payload: data });
};
};
Как вы можете заметить, здесь используется dispatch для передачи данных в reducer. Однако в этом примере отсутствует типизация, из-за чего легко допустить ошибки. Давайте добавим немного волшебства TypeScript!
Типизация Thunk: первые шаги
Redux Toolkit предоставляет полезный тип ThunkAction из пакета redux-thunk, который позволяет описать типы Thunk-функций. Начнём с типизации нашего dispatch.
Типизация AppDispatch
dispatch — это центральная точка взаимодействия с нашим Redux-хранилищем. Чтобы типизировать dispatch, нам нужно описать, какие действия он может обрабатывать.
import { configureStore } from "@reduxjs/toolkit";
import { ThunkAction } from "redux-thunk";
// Типизация Redux Store
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// Пример создания store
const store = configureStore({
reducer: {
// ваши редьюсеры
},
});
Теперь мы можем типизировать dispatch в наших Thunk-функциях.
Типизация ThunkAction
Используем ThunkAction, чтобы описать структуру Thunk. Вот основные параметры, которые принимает ThunkAction:
ReturnType: что вернёт наша функция (обычно этоvoid).State: состояние Redux (нашRootState).ExtraThunkArg: дополнительные аргументы (редко используется, поэтому часто указываемunknown).BasicAction: базовый тип Redux Action.
Давайте посмотрим на типизированную Thunk-функцию:
import { ThunkAction } from "redux-thunk";
import { RootState } from "../store"; // Ваш типизированный стейт
type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
export const fetchUserData = (): AppThunk => {
return async (dispatch, getState) => {
const response = await fetch(`/api/users`);
const data = await response.json();
dispatch({ type: "SET_USER_DATA", payload: data });
};
};
Теперь, если вы передадите неверный тип данных в dispatch, TypeScript немедленно сигнализирует об ошибке.
Типизация API-ответов
Одной из главных причин использовать TypeScript с Redux Thunk является возможность типизации данных, которые ваша Thunk-функция получает от API. Давайте рассмотрим пример.
Описание интерфейса API-ответа
Предположим, что наш API возвращает следующую структуру данных пользователя:
{
"id": 1,
"name": "John Doe",
"email": "john.doe@example.com"
}
Создадим интерфейс для этих данных:
interface User {
id: number;
name: string;
email: string;
}
Использование интерфейса в Thunk
Теперь мы можем типизировать наш Thunk, чтобы TypeScript знал, какие данные мы ожидаем от API.
import { ThunkAction } from "redux-thunk";
import { RootState } from "../store";
import { Action } from "redux";
// Интерфейс для API-ответа
interface User {
id: number;
name: string;
email: string;
}
type AppThunk = ThunkAction<void, RootState, unknown, Action<string>>;
export const fetchUserData = (): AppThunk => {
return async (dispatch, getState) => {
const response = await fetch(`/api/users`);
const data: User[] = await response.json(); // Типизируем данные как массив пользователей
dispatch({ type: "SET_USER_DATA", payload: data });
};
};
Теперь, если API изменит структуру данных, TypeScript сообщит об этом заранее.
Типизация состояния (state) и действий (actions)
Давайте разберемся, как типизировать Redux-состояние и действия. Это основа для строгой типизации всего Redux-приложения.
Типизация состояния
Сначала определим интерфейс нашего состояния:
interface UserState {
users: User[];
isLoading: boolean;
error: string | null;
}
Затем добавим это состояние в наш reducer:
const initialState: UserState = {
users: [],
isLoading: false,
error: null,
};
export const userReducer = (state = initialState, action: UserActions): UserState => {
switch (action.type) {
case "SET_USER_DATA":
return { ...state, users: action.payload };
case "SET_LOADING":
return { ...state, isLoading: action.payload };
default:
return state;
}
};
Типизация действий
Определим интерфейсы для наших действий:
interface SetUserDataAction {
type: "SET_USER_DATA";
payload: User[];
}
interface SetLoadingAction {
type: "SET_LOADING";
payload: boolean;
}
type UserActions = SetUserDataAction | SetLoadingAction;
Теперь мы можем типизировать все действия в нашем приложении.
Практическое применение типизированных Thunk
Давайте соберём всё вместе и создадим типизированное приложение.
// actions.ts
export const fetchUserData = (): AppThunk => {
return async (dispatch, getState) => {
dispatch({ type: "SET_LOADING", payload: true });
try {
const response = await fetch(`/api/users`);
const data: User[] = await response.json();
dispatch({ type: "SET_USER_DATA", payload: data });
} catch (error) {
dispatch({ type: "SET_ERROR", payload: error.message });
} finally {
dispatch({ type: "SET_LOADING", payload: false });
}
};
};
Типичные ошибки
Не забывайте об основных проблемах, с которыми вы можете столкнуться:
- Вы можете забыть типизировать
RootStateилиAppDispatch. Проверьте, чтобыstoreбыл корректно настроен. - Если вы используете неправильные интерфейсы для API-ответа, TypeScript не сможет защитить вас от ошибок.
- Не используйте
any— это уничтожает магию TypeScript.
На этом этапе вы готовы использовать типизированные Redux Thunk-функции в своих проектах. В будущем вы оцените, насколько TypeScript упрощает масштабирование и поддержку вашего кода!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ