Как работать с асинхронными действиями в Redux

На список статей
Blog image

Track My Time — Управляй временем эффективно и достигай большего!
"Ваш незаменимый помощник в управлении проектами и учете времени! Отслеживайте задачи, распределяйте ресурсы и контролируйте каждую минуту работы. Повышайте эффективность, упрощайте процессы и достигайте результатов быстрее. Начните работать с нами и добейтесь успеха вместе!"

Redux — это библиотека для JavaScript, которая помогает управлять состоянием приложения предсказуемым образом. Основная идея Redux заключается в том, что вся логика приложения сводится к единому объекту состояния (state), который можно изменять только с помощью определённых действий (actions).

Но что делать, когда нам нужно отправить запрос на сервер, получить данные и изменить состояние на основе этих данных? Поскольку JavaScript по своей природе однопоточный, любые действия, связанные с ожиданием данных, например запросы к серверу, являются асинхронными. Вот здесь и начинаются сложности, ведь Redux был разработан с учётом синхронных действий. Для решения этих проблем используется несколько техник, о которых мы расскажем дальше.

Асинхронные действия и Redux

Прежде чем мы перейдём к примерам, давайте определим, что такое асинхронные действия. В контексте Redux, асинхронные действия — это операции, результат которых не может быть получен мгновенно. К таким действиям относятся:

  • Запросы к API (получение или отправка данных);
  • Чтение файлов;
  • Взаимодействие с базой данных.

Асинхронные действия важны, потому что они позволяют приложению работать с внешними источниками данных. Например, когда вы запрашиваете данные о пользователе с сервера, требуется некоторое время, прежде чем ответ будет получен. Это время нужно корректно обрабатывать, чтобы не нарушать работу приложения.

Как обрабатывать асинхронные действия в Redux?

Асинхронные действия не обрабатываются в чистом Redux "из коробки". Поэтому разработчики используют несколько популярных библиотек для добавления этой функциональности:

  1. Redux Thunk — один из самых простых и распространённых способов для обработки асинхронных действий.
  2. Redux Saga — более сложный, но мощный инструмент для управления побочными эффектами в Redux.
  3. Redux Toolkit — современный инструмент, который упрощает настройку Redux, включая поддержку асинхронных действий.

Для начинающих мы рекомендуем начать с Redux Thunk, так как он наиболее интуитивен и прост в использовании. Мы подробно рассмотрим, как его установить и начать использовать.


Шаг 1: Установка и настройка Redux Thunk

Первое, что нам нужно сделать, — это установить библиотеку redux-thunk:

npm install redux-thunk

После этого необходимо добавить thunk как middleware в ваше Redux-хранилище (store). В configureStore или при создании store вручную, вы добавляете middleware следующим образом:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const store = createStore(rootReducer, applyMiddleware(thunk));

Теперь ваше хранилище готово к работе с асинхронными действиями.

Шаг 2: Создание асинхронного действия

Предположим, что вы хотите сделать запрос к API для получения данных о пользователях. Вместо того, чтобы делать запрос прямо в компоненте, мы создадим асинхронное действие. В Redux Thunk асинхронные действия представляют собой функции, которые возвращают другую функцию, принимающую dispatch как аргумент.

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

export const fetchUsers = () => {
 return async (dispatch) => {
   dispatch({ type: 'FETCH_USERS_REQUEST' });
   
   try {
     const response = await fetch('https://jsonplaceholder.typicode.com/users');
     const data = await response.json();
     
     dispatch({ type: 'FETCH_USERS_SUCCESS', payload: data });
   } catch (error) {
     dispatch({ type: 'FETCH_USERS_FAILURE', error: error.message });
   }
 };
};

Здесь мы делаем следующее:

  1. Диспетчеризуем действие FETCH_USERS_REQUEST, которое сигнализирует, что запрос начался.
  2. Используем fetch для отправки запроса на сервер.
  3. После получения ответа диспетчеризуем действие FETCH_USERS_SUCCESS с полученными данными.
  4. Если произошла ошибка, диспетчеризуем действие FETCH_USERS_FAILURE с сообщением об ошибке.

Шаг 3: Обработка состояний запроса

Для каждого асинхронного запроса у нас должно быть три состояния:

  1. Начало запроса — когда данные ещё не получены, и мы можем показать индикатор загрузки.
  2. Успешное выполнение — когда данные получены и мы их отображаем.
  3. Ошибка — если запрос завершился неудачей.

Наш редьюсер должен обрабатывать эти три состояния:

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

const usersReducer = (state = initialState, action) => {
 switch (action.type) {
   case 'FETCH_USERS_REQUEST':
     return {
       ...state,
       loading: true
     };
   case 'FETCH_USERS_SUCCESS':
     return {
       ...state,
       loading: false,
       users: action.payload
     };
   case 'FETCH_USERS_FAILURE':
     return {
       ...state,
       loading: false,
       error: action.error
     };
   default:
     return state;
 }
};

export default usersReducer;

Здесь наш редьюсер обновляет состояние в зависимости от того, какое действие было диспетчеризовано:

  • Если начался запрос (FETCH_USERS_REQUEST), то мы включаем индикатор загрузки.
  • Если запрос успешен (FETCH_USERS_SUCCESS), то сохраняем данные и выключаем индикатор загрузки.
  • Если запрос завершился ошибкой (FETCH_USERS_FAILURE), то сохраняем сообщение об ошибке.

Шаг 4: Использование в компонентах

Теперь, когда наши действия и редьюсер готовы, мы можем использовать их в React-компонентах с помощью useDispatch и useSelector из react-redux.

Пример компонента, который отображает список пользователей:

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUsers } from './actions/usersActions';

const UsersList = () => {
 const dispatch = useDispatch();
 
 const { users, loading, error } = useSelector(state => state.users);
 
 useEffect(() => {
   dispatch(fetchUsers());
 }, [dispatch]);

 if (loading) {
   return <div>Loading...</div>;
 }

 if (error) {
   return <div>Error: {error}</div>;
 }

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

export default UsersList;

Здесь:

  • Мы используем useEffect для того, чтобы запустить запрос при загрузке компонента.
  • useDispatch позволяет отправить действие fetchUsers().
  • useSelector используется для получения состояния пользователей, индикатора загрузки и ошибки из нашего Redux-хранилища.

Шаг 5: Обработка сложных сценариев

В реальных приложениях запросы к серверу могут быть более сложными, требуя обработки нескольких запросов одновременно или последовательной загрузки данных. Redux Thunk может справиться с этим, так как он поддерживает композицию действий.

Например, если вам нужно отправить несколько запросов последовательно, вы можете сделать это следующим образом:

export const fetchUserData = (userId) => {
 return async (dispatch) => {
   dispatch({ type: 'FETCH_USER_REQUEST' });

   try {
     const userResponse = await fetch(`https://api.example.com/users/${userId}`);
     const userData = await userResponse.json();
     
     dispatch({ type: 'FETCH_USER_SUCCESS', payload: userData });
     
     const postsResponse = await fetch(`https://api.example.com/users/${userId}/posts`);
     const postsData = await postsResponse.json();
     
     dispatch({ type: 'FETCH_USER_POSTS_SUCCESS', payload: postsData });
   } catch (error) {
     dispatch({ type: 'FETCH_USER_FAILURE', error: error.message });
   }
 };
};

Здесь после успешного получения данных о пользователе, мы делаем второй запрос для получения его постов и также диспетчеризуем результат.

Комментарии

Пока нет комментариев

Добавить комментарий