← Назад к вопросам
Как работать с асинхронным кодом в Redux?
2.0 Middle🔥 241 комментариев
#State Management#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Асинхронный код в Redux
Redux по умолчанию не поддерживает асинхронные операции, поэтому нужно использовать middleware или современные инструменты. Рассмотрим основные подходы.
1. Redux Thunk - классический подход
Thunk - это функция, которая возвращает другую функцию для отложенного выполнения:
import { createSlice, configureStore } from "@reduxjs/toolkit";
import thunk from "redux-thunk";
const userSlice = createSlice({
name: "user",
initialState: { data: null, loading: false, error: null },
reducers: {
startLoading: (state) => { state.loading = true; },
setUser: (state, action) => { state.data = action.payload; state.loading = false; },
setError: (state, action) => { state.error = action.payload; state.loading = false; }
}
});
// Thunk action
const fetchUser = (userId) => async (dispatch) => {
dispatch(startLoading());
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
dispatch(setUser(data));
} catch (error) {
dispatch(setError(error.message));
}
};
const store = configureStore({
reducer: { user: userSlice.reducer },
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(thunk)
});
2. Redux Thunk в компоненте
Использование thunk action в React компоненте:
function UserComponent() {
const dispatch = useDispatch();
const { data, loading, error } = useSelector((state) => state.user);
useEffect(() => {
dispatch(fetchUser(123));
}, [dispatch]);
if (loading) return <div>Загрузка...</div>;
if (error) return <div>Ошибка: {error}</div>;
return <div>{data?.name}</div>;
}
3. Redux Toolkit с createAsyncThunk
Современный и рекомендуемый подход (Redux Toolkit):
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
// Определение async thunk
const fetchUser = createAsyncThunk(
"user/fetchUser",
async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
);
const userSlice = createSlice({
name: "user",
initialState: { data: null, loading: false, error: null },
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = false;
})
.addCase(fetchUser.rejected, (state, action) => {
state.error = action.error.message;
state.loading = false;
});
}
});
export default userSlice.reducer;
export { fetchUser };
4. Redux Saga - продвинутый подход
Использует генераторы для управления асинхронностью:
import { put, call, takeEvery } from "redux-saga/effects";
// Saga функция (генератор)
function* fetchUserSaga(action) {
try {
const response = yield call(
fetch,
`/api/users/${action.payload}`
);
const data = yield call(() => response.json());
yield put(setUser(data));
} catch (error) {
yield put(setError(error.message));
}
}
// Корневая saga
function* rootSaga() {
yield takeEvery("FETCH_USER", fetchUserSaga);
}
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(rootSaga);
5. Redux Observable - с RxJS
Для тех, кто работает с реактивным программированием:
import { ofType } from "redux-observable";
import { switchMap, map, catchError } from "rxjs/operators";
const fetchUserEpic = (action$) =>
action$.pipe(
ofType("FETCH_USER"),
switchMap((action) =>
fetch(`/api/users/${action.payload}`).then((r) => r.json()).pipe(
map((data) => setUser(data)),
catchError((error) => of(setError(error.message)))
)
)
);
6. Обработка ошибок и повторы
Реализация retry logic при ошибке:
const fetchUserWithRetry = createAsyncThunk(
"user/fetchUser",
async (userId, { rejectWithValue }) => {
let lastError;
for (let attempt = 0; attempt < 3; attempt++) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
lastError = error;
if (attempt < 2) {
await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)));
}
}
}
return rejectWithValue(lastError.message);
}
);
7. Сравнение подходов
// Redux Thunk: простой и прямолинейный
const fetchData = () => async (dispatch) => {
const data = await api.get("/data");
dispatch(setData(data));
};
// Redux Saga: больше контроля и сложности
function* fetchDataSaga() {
const data = yield call(api.get, "/data");
yield put(setData(data));
}
// createAsyncThunk: современный и удобный
const fetchData = createAsyncThunk("data/fetch", () => api.get("/data"));
8. Лучшие практики
// 1. Используй loading states для каждого запроса
const userSlice = createSlice({
initialState: {
entities: {},
loading: {},
error: {}
}
});
// 2. Нормализуй состояние
const state = {
users: {
1: { id: 1, name: "Alice" },
2: { id: 2, name: "Bob" }
}
};
// 3. Используй requestIdGenerator для отмены запросов
const fetchUser = createAsyncThunk(
"user/fetch",
async (userId, { signal }) => {
return fetch(`/api/users/${userId}`, { signal });
}
);
9. Рекомендация
Для новых проектов рекомендую:
- Redux Toolkit + createAsyncThunk - стандартный выбор
- Redux Saga - если нужна сложная логика с множественными побочными эффектами
- Redux Thunk - для простых проектов
- RTK Query - если нужно управлять кешированием данных с сервера
Мой опыт: createAsyncThunk из Redux Toolkit - лучший выбор для большинства приложений. Он предоставляет удобный API и автоматически генерирует pending/fulfilled/rejected действия.