Как работать с асинхронными действиями в Redux
Redux — это библиотека для JavaScript, которая помогает управлять состоянием приложения предсказуемым образом. Основная идея Redux заключается в том, что вся логика приложения сводится к единому объекту состояния (state), который можно изменять только с помощью определённых действий (actions).
Но что делать, когда нам нужно отправить запрос на сервер, получить данные и изменить состояние на основе этих данных? Поскольку JavaScript по своей природе однопоточный, любые действия, связанные с ожиданием данных, например запросы к серверу, являются асинхронными. Вот здесь и начинаются сложности, ведь Redux был разработан с учётом синхронных действий. Для решения этих проблем используется несколько техник, о которых мы расскажем дальше.
Асинхронные действия и Redux
Прежде чем мы перейдём к примерам, давайте определим, что такое асинхронные действия. В контексте Redux, асинхронные действия — это операции, результат которых не может быть получен мгновенно. К таким действиям относятся:
- Запросы к API (получение или отправка данных);
- Чтение файлов;
- Взаимодействие с базой данных.
Асинхронные действия важны, потому что они позволяют приложению работать с внешними источниками данных. Например, когда вы запрашиваете данные о пользователе с сервера, требуется некоторое время, прежде чем ответ будет получен. Это время нужно корректно обрабатывать, чтобы не нарушать работу приложения.
Как обрабатывать асинхронные действия в Redux?
Асинхронные действия не обрабатываются в чистом Redux "из коробки". Поэтому разработчики используют несколько популярных библиотек для добавления этой функциональности:
- Redux Thunk — один из самых простых и распространённых способов для обработки асинхронных действий.
- Redux Saga — более сложный, но мощный инструмент для управления побочными эффектами в Redux.
- 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 });
}
};
};
Здесь мы делаем следующее:
- Диспетчеризуем действие FETCH_USERS_REQUEST, которое сигнализирует, что запрос начался.
- Используем fetch для отправки запроса на сервер.
- После получения ответа диспетчеризуем действие FETCH_USERS_SUCCESS с полученными данными.
- Если произошла ошибка, диспетчеризуем действие FETCH_USERS_FAILURE с сообщением об ошибке.
Шаг 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 });
}
};
};
Здесь после успешного получения данных о пользователе, мы делаем второй запрос для получения его постов и также диспетчеризуем результат.






Комментарии