JavaRush /Курсы /Модуль 3: React /Пример создания асинхронного действия с Redux Saga

Пример создания асинхронного действия с Redux Saga

Модуль 3: React
7 уровень , 8 лекция
Открыта

Часть 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;

Здесь у нас три действия:

  1. fetchUsersRequest — для начала загрузки.
  2. fetchUsersSuccess — для успешного завершения запроса.
  3. 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!

1
Задача
Модуль 3: React, 7 уровень, 8 лекция
Недоступна
Создание Slice для управления состоянием
Создание Slice для управления состоянием
1
Задача
Модуль 3: React, 7 уровень, 8 лекция
Недоступна
Интеграция Redux Saga для загрузки данных
Интеграция Redux Saga для загрузки данных
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ