Что такое Redux Thunk?
Redux Thunk — это библиотека, которая позволяет нам выполнять асинхронные действия внутри Redux. Давайте упростим: в обычной ситуации Redux работает только с синхронными действиями. То есть, вы отправляете dispatch действие — и оно сразу попадает в редьюсер. Однако, если вам нужно подождать, например, ответа от сервера, Redux сам по себе не сможет это сделать.
Вот тут на сцену выходит Thunk! Он предоставляет возможность отправлять функции вместо объектов-действий. Эти функции, в свою очередь, могут выполнять асинхронные операции и отправлять другие действия в Redux.
Представьте, что Redux — это ресторан. Официант (Redux) берёт ваш заказ (действие) и передаёт его на кухню (редьюсер). Но если ваше блюдо ещё не готово (асинхронные запросы), официант теряется. Redux Thunk — это шеф-повар, который может дождаться готовности блюда (завершения запроса) и только потом передать его на кухню.
Установка Redux Thunk
Первый шаг — добавить Thunk в наш проект. Для этого просто установите библиотеку:
npm install redux-thunk
Теперь подключим его к нашему Store. Откроем файл с настройкой Redux Store (например, store.ts):
import { configureStore } from '@reduxjs/toolkit';
import thunk from 'redux-thunk';
const store = configureStore({
reducer: {
// ваши редьюсеры
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(thunk),
});
export default store;
Вот и всё, Thunk подключён! Лёгкая победа.
Как работает Thunk?
Redux Thunk позволяет вам отправлять в dispatch не только действия, но и функции. Эти функции принимают два аргумента:
dispatch— чтобы отправлять действия.getState— чтобы получить текущее состояние.
Пример простой Thunk-функции:
const fetchData = () => {
return async (dispatch, getState) => {
dispatch({ type: 'FETCH_DATA_START' }); // Начать загрузку данных
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data }); // Успешная загрузка
} catch (error) {
dispatch({ type: 'FETCH_DATA_FAILURE', error }); // Ошибка загрузки
}
};
};
Эта функция:
- Стартует загрузку данных.
- Выполняет асинхронный запрос с помощью
fetch. - По завершении отправляет в Redux новое действие с результатом
FETCH_DATA_SUCCESSилиFETCH_DATA_FAILURE.
Теперь внедрим это в наше приложение. Допустим, мы разрабатываем простое приложение, отображающее список статей.
1. Создадим редьюсер
Файл: features/posts/postsSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface Post {
id: number;
title: string;
body: string;
}
interface PostsState {
loading: boolean;
posts: Post[];
error: string | null;
}
const initialState: PostsState = {
loading: false,
posts: [],
error: null,
};
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
fetchPostsStart(state) {
state.loading = true;
state.error = null;
},
fetchPostsSuccess(state, action: PayloadAction<Post[]>) {
state.loading = false;
state.posts = action.payload;
},
fetchPostsFailure(state, action: PayloadAction<string>) {
state.loading = false;
state.error = action.payload;
},
},
});
export const { fetchPostsStart, fetchPostsSuccess, fetchPostsFailure } =
postsSlice.actions;
export default postsSlice.reducer;
2. Создадим Thunk
Файл: features/posts/postsThunks.ts
import { fetchPostsStart, fetchPostsSuccess, fetchPostsFailure } from './postsSlice';
import { AppDispatch } from '../../store';
export const fetchPosts = () => {
return async (dispatch: AppDispatch) => {
dispatch(fetchPostsStart()); // Начало загрузки
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
dispatch(fetchPostsSuccess(data)); // Успех
} catch (error: any) {
dispatch(fetchPostsFailure(error.message)); // Ошибка
}
};
};
3. Интеграция с компонентом
Теперь используем созданный Thunk в компоненте React.
Файл: components/PostsList.tsx
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchPosts } from '../features/posts/postsThunks';
import { RootState } from '../store';
const PostsList: React.FC = () => {
const dispatch = useDispatch();
const { posts, loading, error } = useSelector((state: RootState) => state.posts);
useEffect(() => {
dispatch(fetchPosts());
}, [dispatch]);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error}</p>;
}
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
};
export default PostsList;
Что мы получили?
Теперь наше приложение:
- Отправляет асинхронный запрос.
- Использует Thunk для управления состоянием загрузки и ошибок.
- Динамически обновляет интерфейс на основе ответа от API.
Какие типичные ошибки бывают?
Итак, Redux Thunk — это мощный инструмент, но есть несколько подводных камней. Во-первых, не забывайте ловить ошибки при запросах. Если пропустить блок catch, ваше приложение может упасть из-за необработанного исключения.
Во-вторых, следите за тем, чтобы не допустить гонки состояний. Например, если пользователь отправляет несколько запросов подряд, старые данные могут перезаписать новые. Для таких случаев лучше хранить ID активного запроса и игнорировать результаты старых.
Зачем это нужно на практике?
Redux Thunk активно используется в реальных проектах, где нужно взаимодействовать с API. Например:
- Подгрузка данных с сервера при загрузке страницы.
- Отправка форм на сервер и обработка ошибок.
- Управление пользователем: вход/выход, проверка токенов.
После освоения Thunk вы сможете легко реализовывать асинхронную логику в любом Redux-приложении.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ