JavaRush /Курсы /Модуль 3: React /Использование createAsyncThunk для обработки асинхронных ...

Использование createAsyncThunk для обработки асинхронных действий

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

Асинхронные процессы на клиенте

Асинхронные действия — это операции, выполнение которых занимает время. Например, запрос данных с сервера, отправка формы или обновление профиля пользователя. Они могут включать несколько состояний, таких как:

  • Загрузка данных (loading),
  • Успешное завершение (success),
  • Ошибка (error).

Для таких операций раньше мы могли использовать middleware, такие как redux-thunk, и писать много шаблонного кода. Но зачем усложнять себе жизнь, когда есть createAsyncThunk, который упрощает этот процесс.

Преимущества createAsyncThunk

  1. Упрощает создание асинхронных операций.
  2. Автоматически обрабатывает состояния загрузки, успешного и ошибочного завершения.
  3. Поддерживает строгую типизацию с TypeScript.
  4. Интегрируется с createSlice для управления состоянием.

Создание асинхронного экшена с createAsyncThunk

Давайте начнём с практического примера. Мы добавим функциональность для загрузки данных пользователей из API.

Обязательно убедитесь, что у вас установлен @reduxjs/toolkit и настроен Store.

Создаём асинхронный экшен

Добавим новый срез usersSlice для работы с данными пользователей. Начнём с createAsyncThunk.

import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';

interface User {
  id: number;
  name: string;
  email: string;
}

interface UsersState {
  users: User[];
  loading: boolean;
  error: string | null;
}

const initialState: UsersState = {
  users: [],
  loading: false,
  error: null,
};

// Асинхронный экшен для получения пользователей
export const fetchUsers = createAsyncThunk<User[], void>(
  'users/fetchUsers',
  async (_, { rejectWithValue }) => {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      if (!response.ok) {
        throw new Error('Ошибка загрузки данных');
      }
      return response.json();
    } catch (error: any) {
      return rejectWithValue(error.message);
    }
  }
);

Разбор кода:

  1. Импорт createAsyncThunk: это основной инструмент для создания асинхронных экшенов.
  2. Типизация: мы указываем, что fetchUsers возвращает массив пользователей User[].
  3. rejectWithValue: позволяет передать ошибку в случай обработки неудачи (об этом чуть позже).

Добавление обработчиков в Slice

Теперь подключим наш асинхронный экшен к usersSlice. Мы опишем, как состояние должно изменяться на каждом этапе выполнения.

const usersSlice = createSlice({
  name: 'users',
  initialState,
  reducers: {}, // Здесь можно добавить обычные редьюсеры
  extraReducers: (builder) => {
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchUsers.fulfilled, (state, action: PayloadAction<User[]>) => {
        state.loading = false;
        state.users = action.payload;
      })
      .addCase(fetchUsers.rejected, (state, action: PayloadAction<any>) => {
        state.loading = false;
        state.error = action.payload;
      });
  },
});

export const usersReducer = usersSlice.reducer;

Что происходит:

  • fetchUsers.pending: срабатывает, когда запрос отправлен, но данные ещё не загружены. Мы выставляем loading в true и очищаем ошибку.
  • fetchUsers.fulfilled: вызвается, если данные успешно загружены. Мы сохраняем пользователей в состоянии.
  • fetchUsers.rejected: если запрос завершился ошибкой, мы сохраняем сообщение об ошибке и отключаем индикатор загрузки.

Интеграция с компонентами

Шаг 1: Подключаем редьюсер в Store

В store.ts добавьте новый редьюсер:

import { configureStore } from '@reduxjs/toolkit';
import { usersReducer } from './usersSlice';

export const store = configureStore({
  reducer: {
    users: usersReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Шаг 2: Используем FetchUsers в компоненте

Теперь создадим компонент UsersList, который будет отображать пользователей.

import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUsers } from './usersSlice';
import { RootState, AppDispatch } from './store';

const UsersList: React.FC = () => {
  const dispatch = useDispatch<AppDispatch>();
  const { users, loading, error } = useSelector((state: RootState) => state.users);

  useEffect(() => {
    dispatch(fetchUsers());
  }, [dispatch]);

  if (loading) {
    return <p>Загрузка...</p>;
  }

  if (error) {
    return <p>Ошибка: {error}</p>;
  }

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          {user.name} - {user.email}
        </li>
      ))}
    </ul>
  );
};

export default UsersList;

Разбор:

  1. useEffect с dispatch(fetchUsers()): мы вызываем асинхронный экшен в момент монтирования компонента.
  2. Доступ к данным: useSelector извлекает пользователей, состояние загрузки и ошибки из Store.
  3. Рендеринг состояния: мы показываем индикатор загрузки, сообщение об ошибке или список пользователей в зависимости от состояния.

Реальные кейсы и советы

  • Обработка ошибок. Всегда обрабатывайте ошибки с помощью rejectWithValue, чтобы иметь ясное сообщение об ошибке в состоянии.
  • Повторное использование. Асинхронные экшены могут быть переиспользованы в разных частях приложения.
  • Типизация. Правильная типизация упрощает использование данных и предотвращает ошибки.
  • Оптимизация. Используйте Selectors для мемоизации данных и сокращения перерисовок компонентов.
1
Задача
Модуль 3: React, 6 уровень, 6 лекция
Недоступна
Интеграция асинхронного экшена в slice
Интеграция асинхронного экшена в slice
1
Задача
Модуль 3: React, 6 уровень, 6 лекция
Недоступна
Создание React-компонента с отображением данных
Создание React-компонента с отображением данных
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ