Tutorial

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

Published on December 1, 2020
    authorauthor

    Alligator.io and Bradley Kouchi

    Русский
    Асинхронные действия Redux с Redux Thunk

    Введение

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

    Существует две очень популярные библиотеки промежуточного ПО, поддерживающие побочные эффекты и асинхронные действия: Redux Thunk и Redux Saga. В этой статье мы расскажем о Redux Thunk.

    Thunk или преобразователь — это концепция программирования, в которой функция используется для отсрочки оценки или расчета операции.

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

    Из этой статьи вы узнаете, как добавить Redux Thunk и использовать его для гипотетического приложения Todo.

    Предварительные требования

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

    Этот учебный модуль построен на основе гипотетического приложения Todo, которое отслеживает задачи, требующие выполнения, и уже выполненные задачи. Можно предположить, что приложение create-react-app было использовано для генерирования нового приложения React, и что redux, react-redux и axios уже установлены.

    Здесь не разъясняются более подробные детали процедуры создания приложения Todo с нуля. Оно представлено как концептуальная основа для разъяснения Redux Thunk.

    Добавление redux-thunk

    Прежде всего, используйте терминал для перехода в каталог проекта и установите пакет redux-thunk в ваш проект:

    1. npm install redux-thunk@2.3.0

    Примечание. Redux Thunk содержит всего 14 строк кода. Посмотрите исходный код здесь, чтобы узнать о принципах работы промежуточного ПО Redux.

    Примените промежуточное ПО при создании магазина вашего приложения, используя команду Redux applyMiddleware. Если использовать приложение React с redux и react-redux, ваш файл index.js будет выглядеть следующим образом:

    src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { Provider } from 'react-redux';
    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import './index.css';
    import rootReducer from './reducers';
    import App from './App';
    import * as serviceWorker from './serviceWorker';
    
    // use applyMiddleware to add the thunk middleware to the store
    const store = createStore(rootReducer, applyMiddleware(thunk));
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    

    Итак, мы импортировали Redux Thunk и добавили его в наше приложение.

    Использование Redux Thunk в образце приложения

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

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

    Давайте посмотрим, как сделать это с помощью Redux Thunk.

    Импортируйте действие в компонент контейнера и обработайте его:

    src/containers/AddTodo.js
    import { connect } from 'react-redux';
    import { addTodo } from '../actions';
    import NewTodo from '../components/NewTodo';
    
    const mapDispatchToProps = dispatch => {
      return {
        onAddTodo: todo => {
          dispatch(addTodo(todo));
        }
      };
    };
    
    export default connect(
      null,
      mapDispatchToProps
    )(NewTodo);
    

    Действие использует Axios для отправки запроса POST на конечную точку в JSONPlaceholder (https://jsonplaceholder.typicode.com/todos):

    src/actions/index.js
    import {
      ADD_TODO_SUCCESS,
      ADD_TODO_FAILURE,
      ADD_TODO_STARTED,
      DELETE_TODO
    } from './types';
    
    import axios from 'axios';
    
    export const addTodo = ({ title, userId }) => {
      return dispatch => {
        dispatch(addTodoStarted());
    
        axios
          .post(`https://jsonplaceholder.typicode.com/todos`, {
            title,
            userId,
            completed: false
          })
          .then(res => {
            dispatch(addTodoSuccess(res.data));
          })
          .catch(err => {
            dispatch(addTodoFailure(err.message));
          });
      };
    };
    
    const addTodoSuccess = todo => ({
      type: ADD_TODO_SUCCESS,
      payload: {
        ...todo
      }
    });
    
    const addTodoStarted = () => ({
      type: ADD_TODO_STARTED
    });
    
    const addTodoFailure = error => ({
      type: ADD_TODO_FAILURE,
      payload: {
        error
      }
    });
    

    Обратите внимание на то, как создатель действия addTodo возвращает функцию вместо обычного объекта действия. Эта функция получает метод обработки из магазина.

    В теле функции вы вначале отправляете немедленное синхронное действие в магазин, чтобы показать, что вы начали сохранение элемента todo с внешним API. Затем вы отправляете на сервер фактический запрос POST, используя Axios. При успешном ответе сервера вы отправляете синхронное действие успеха с данными, полученными в составе ответа, однако в случае неудачи мы отправляем еще одно синхронное действие с сообщением об ошибке.

    При использовании внешнего API, например, JSONPlaceholder в приведенном примере, может возникнуть реальная задержка сети. Однако, если вы работаете с локальным сервером, ответ сети может быть получен слишком быстро, чтобы получить такую же задержку, с какой столкнется конечный пользователь, так что вы можете добавить искусственную задержку при разработке:

    src/actions/index.js
    // ...
    
    export const addTodo = ({ title, userId }) => {
      return dispatch => {
        dispatch(addTodoStarted());
    
        axios
          .post(ENDPOINT, {
            title,
            userId,
            completed: false
          })
          .then(res => {
            setTimeout(() => {
              dispatch(addTodoSuccess(res.data));
            }, 2500);
          })
          .catch(err => {
            dispatch(addTodoFailure(err.message));
          });
      };
    };
    
    // ...
    

    Чтобы протестировать сценарии ошибок, вы можете создать ошибку вручную:

    src/actions/index.js
    // ...
    
    export const addTodo = ({ title, userId }) => {
      return dispatch => {
        dispatch(addTodoStarted());
    
        axios
          .post(ENDPOINT, {
            title,
            userId,
            completed: false
          })
          .then(res => {
            throw new Error('addToDo error!');
            // dispatch(addTodoSuccess(res.data));
          })
          .catch(err => {
            dispatch(addTodoFailure(err.message));
          });
      };
    };
    
    // ...
    

    Для полноты приведем пример того, как может выглядеть редуктор todo для обработки полного жизненного цикла запроса:

    src/reducers/todosReducer.js
    import {
      ADD_TODO_SUCCESS,
      ADD_TODO_FAILURE,
      ADD_TODO_STARTED,
      DELETE_TODO
    } from '../actions/types';
    
    const initialState = {
      loading: false,
      todos: [],
      error: null
    };
    
    export default function todosReducer(state = initialState, action) {
      switch (action.type) {
        case ADD_TODO_STARTED:
          return {
            ...state,
            loading: true
          };
        case ADD_TODO_SUCCESS:
          return {
            ...state,
            loading: false,
            error: null,
            todos: [...state.todos, action.payload]
          };
        case ADD_TODO_FAILURE:
          return {
            ...state,
            loading: false,
            error: action.payload.error
          };
        default:
          return state;
      }
    }
    

    Изучение getState

    После получения метода dispatch от состояния функция, возвращаемая создателем асинхронного действия с Redux Thunk, также получает метод getState магазина, что позволяет прочитать текущие значения магазина:

    src/actions/index.js
    export const addTodo = ({ title, userId }) => {
      return (dispatch, getState) => {
        dispatch(addTodoStarted());
    
        console.log('current state:', getState());
    
        // ...
      };
    };
    

    В вышеперечисленном случае текущее состояние просто распечатывается на консоли.

    Например:

    {loading: true, todos: Array(1), error: null}
    

    Использование getState может быть полезно для того, чтобы обрабатывать действия по-разному в зависимости от текущего состояния. Например, если вы захотите ограничить возможности приложения одновременной обработкой только четырех элементов todo, вы можете выполнять возврат из функции, если состояние уже содержит максимальное количество элементов todo:

    src/actions/index.js
    export const addTodo = ({ title, userId }) => {
      return (dispatch, getState) => {
        const { todos } = getState();
    
        if (todos.length > 4) return;
    
        dispatch(addTodoStarted());
    
        // ...
      };
    };
    

    В вышеуказанном случае приложение сможет работать только с четырьмя элементами todo.

    Заключение

    В этом учебном модуле мы изучили добавление Redux Thunk в приложение React для асинхронной обработки действий. Это полезно при использовании магазина Redux и внешних API.

    Если вы хотите узнать больше о React, почитайте нашу серию «Программирование на React.js» или посмотрите страницу тем React, где вы найдете упражнения и программные проекты.

    Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

    Learn more about us


    About the authors
    Default avatar
    Alligator.io

    author



    Still looking for an answer?

    Ask a questionSearch for more help

    Was this helpful?
     
    Leave a comment
    

    This textbox defaults to using Markdown to format your answer.

    You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

    Try DigitalOcean for free

    Click below to sign up and get $200 of credit to try our products over 60 days!

    Sign up

    Join the Tech Talk
    Success! Thank you! Please check your email for further details.

    Please complete your information!

    Featured on Community

    Get our biweekly newsletter

    Sign up for Infrastructure as a Newsletter.

    Hollie's Hub for Good

    Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

    Become a contributor

    Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

    Welcome to the developer cloud

    DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

    Learn more
    DigitalOcean Cloud Control Panel