Часть 1: создаём простую сагу на практике
Сегодня мы создадим пример "асинхронного действия" для вашего приложения с помощью Redux Saga. Если тема асинхронных задач в Redux вас завораживает, как завораживает программиста идея сделать всё максимально чисто и элегантно, то добро пожаловать!
Redux Saga предоставляет нам читабельный и декларативный подход к обработке побочных эффектов. Пример, который мы будем реализовывать, — это классическая задача получения списка пользователей с API.
Шаг 1: Настройка окружения
Перед тем как начать работать, убедитесь, что в вашем проекте установлены redux-saga и @reduxjs/toolkit:
npm install redux-saga @reduxjs/toolkit
Если вы хотите следовать за мной шаг за шагом, создайте простой React + Redux проект, как мы делали раньше. Для нашей задачи будет использоваться JSONPlaceholder API https://jsonplaceholder.typicode.com/users.
Шаг 2: Структура проекта и начальные настройки
Создайте следующую структуру:
src/
└── features/
└── users/
├── usersSlice.ts
└── usersSaga.ts
Часть 2: реализация usersSlice для состояния
Как обычно, начнём с настройки slice, содержащего состояние пользователей и соответствующие действия.
// src/features/users/usersSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface User {
id: number;
name: string;
email: string;
}
interface UsersState {
list: User[];
loading: boolean;
error: string | null;
}
const initialState: UsersState = {
list: [],
loading: false,
error: null,
};
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {
fetchUsersRequest(state) {
state.loading = true;
state.error = null;
},
fetchUsersSuccess(state, action: PayloadAction<User[]>) {
state.list = action.payload;
state.loading = false;
},
fetchUsersFailure(state, action: PayloadAction<string>) {
state.error = action.payload;
state.loading = false;
},
},
});
export const {
fetchUsersRequest,
fetchUsersSuccess,
fetchUsersFailure,
} = usersSlice.actions;
export default usersSlice.reducer;
Здесь у нас три действия:
fetchUsersRequest— для начала загрузки.fetchUsersSuccess— для успешного завершения запроса.fetchUsersFailure— для обработки ошибок.
Часть 3: настройка usersSaga
Теперь создадим usersSaga. Основная задача Redux Saga — перехватить действия, запустить асинхронный запрос и затем отправить новое действие в зависимости от результата.
Создание эффекта для запроса
// src/features/users/usersSaga.ts
import { call, put, takeEvery } from 'redux-saga/effects';
import {
fetchUsersRequest,
fetchUsersSuccess,
fetchUsersFailure,
} from './usersSlice';
// Определим API-запрос
async function fetchUsersFromApi() {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error('Failed to fetch users');
}
return response.json();
}
// Saga для обработки асинхронного запроса
function* fetchUsersSaga() {
try {
const users = yield call(fetchUsersFromApi); // Вызов API
yield put(fetchUsersSuccess(users)); // Отправка success-action
} catch (error: any) {
yield put(fetchUsersFailure(error.message)); // Обработка ошибки
}
}
// Watcher saga для перехвата действия fetchUsersRequest
export function* usersSaga() {
yield takeEvery(fetchUsersRequest.type, fetchUsersSaga);
}
Давайте разберём детали:
call: используется для вызова асинхронной функцииfetchUsersFromApiвнутри саги.put: отправляет новое действие в Redux Store.takeEvery: позволяет наблюдать за каждым вызовомfetchUsersRequestи запускатьfetchUsersSaga.
Часть 4: подключение Saga в Store
Чтобы Redux Saga заработала, нужно подключить её к нашему Store.
// src/app/store.ts
import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import usersReducer from '../features/users/usersSlice';
import { usersSaga } from '../features/users/usersSaga';
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: {
users: usersReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ thunk: false }).concat(sagaMiddleware),
});
sagaMiddleware.run(usersSaga); // Запуск саги
export default store;
export type RootState = ReturnType<typeof store.getState>;
Обратите внимание, что мы отключили thunk в middleware, так как используем Redux Saga.
Часть 5: подключение к компонентам React
Теперь настало время оживить интерфейс. Добавьте новый компонент UsersList.
// src/features/users/UsersList.tsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../../app/store';
import { fetchUsersRequest } from './usersSlice';
const UsersList: React.FC = () => {
const dispatch = useDispatch();
const { list, loading, error } = useSelector((state: RootState) => state.users);
useEffect(() => {
dispatch(fetchUsersRequest()); // Отправка действия для запуска саги
}, [dispatch]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{list.map((user) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
);
};
export default UsersList;
Давайте разберём код:
useSelector: получает данные о пользователях из Store.useDispatch: отправляет действиеfetchUsersRequest, которое запускает сагу.useEffect: автоматически запускает действие при монтировании компонента.
Часть 6: запускаем приложение
Добавьте UsersList в ваш App-компонент, и вы увидите список пользователей, загруженных через Redux Saga. А если возникнет ошибка, она будет обработана и выведена пользователю.
// src/App.tsx
import React from 'react';
import UsersList from './features/users/UsersList';
function App() {
return (
<div className="App">
<h1>Users</h1>
<UsersList />
</div>
);
}
export default App;
Часть 7: обработка ошибок
Обратите внимание на блок try/catch внутри саги. Благодаря ему любые ошибки при API-запросе (например, отсутствие интернета или сбой сервера) будут обработаны и отображены.
Практическое применение
Саги используются в реальных проектах, где есть сложные последовательности операций, например:
- Выполнение нескольких запросов, зависящих друг от друга.
- Обработка веб-сокетов и потоков данных.
- Построение автоматических процессов, например, многошаговой аутентификации.
Использование Sagas позволяет минимизировать повторяющийся код и локализовать логику для удобной поддержки.
Теперь вы знаете, как создавать асинхронные действия с Redux Saga!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ