Tutorial

Compreendendo ações assíncronas do Redux com o Redux Thunk

Published on December 1, 2020
    Default avatar

    By Alligator.io

    Português
    Compreendendo ações assíncronas do Redux com o Redux Thunk

    Introdução

    Por padrão, as ações do Redux são enviadas de forma síncrona, o que é um problema para todos os aplicativos não triviais que precisam se comunicar com uma API externa ou executar efeitos colaterais. O Redux também permite que middleware fique entre uma ação sendo despachada e a ação que atinge os redutores.

    Existem duas bibliotecas de middleware muito populares que permitem efeitos colaterais e ações assíncronas: Redux Thunk e Redux Saga. Neste post, você irá explorar o Redux Thunk.

    Thunk (conversão) é um conceito de programação onde uma função é usada para atrasar a avaliação/cálculo de uma operação.

    O Redux Thunk é um middleware que permite chamar criadores de ação que retornam uma função em vez de um objeto de ação. Essa função recebe o método de expedição do armazenamento, que é usado então para expedir ações síncronas regulares dentro do corpo da função assim que as operações assíncronas forem concluídas.

    Neste artigo, você irá aprender como adicionar o Redux Thunk e como ele pode se encaixar em um aplicativo Todo hipotético.

    Pré-requisitos

    Este post assume que você tenha conhecimento básico do React e do Redux. Confira este post se estiver iniciando com o Redux.

    Este tutorial é construído a partir de um aplicativo Todo hipotético que rastreia tarefas que precisam ser realizadas e foram concluídas. Assume-se que o create-react-app foi usado para gerar um novo aplicativo React, e o redux, react-redux e axios já foram instalados.

    Os detalhes mais finos sobre como criar um aplicativo Todo do zero não serão explicados aqui. Ele será apresentado como um cenário conceitual para evidenciar o Redux Thunk.

    Adicionando o redux-thunk

    Primeiro, use o terminal para navegar até o diretório do projeto e instale o pacote redux-thunk em seu projeto:

    1. npm install redux-thunk@2.3.0

    Nota: o Redux Thunk possui apenas 14 linhas de código. Confira aqui o código fonte para aprender sobre como um middleware Redux funciona nos bastidores.

    Agora, aplique o middleware ao criar o armazenamento do seu aplicativo usando o applyMiddleware do Redux. Em um dado aplicativo React com redux e react-redux, seu arquivo index.js deve ficar assim:

    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')
    );
    

    Agora, o Redux Thunk é importado e aplicado em seu aplicativo.

    Usando o Redux Thunk em um aplicativo de amostra

    O caso de uso mais comum para o Redux Thunk é para se comunicar de forma assíncrona com uma API externa para recuperar ou salvar dados. O Redux Thunk torna mais fácil expedir ações que seguem o ciclo de vida de uma solicitação para uma API externa.

    Criar um novo item de tarefa pendente normalmente envolve primeiro expedir uma ação para indicar que a criação de um item de tarefa pendente foi iniciado. Em seguida, se o item de tarefa for criado com sucesso e retornado pelo servidor externo, expedindo outra ação com o novo item de tarefa. Caso aconteça um erro e a tarefa não seja salva no servidor, uma ação com o erro pode ser expedida em vez disso.

    Vamos ver como isso seria feito usando o Redux Thunk.

    Em seu componente contêiner, importe a ação e emita-a:

    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);
    

    A ação irá usar o Axios para enviar uma solicitação POST ao ponto de extremidade em 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
      }
    });
    

    Observe como o criador de ação addTodo retorna uma função em vez do objeto de ação regular. Essa função recebe o método de expedição do armazenamento.

    Dentro do corpo da função, envia-se primeiro uma ação síncrona imediata para o armazenamento para indicar que iniciou-se o salvamento da tarefa pendente com a API externa. Em seguida, você faz a solicitação POST real ao servidor usando o Axios. No caso de uma resposta bem-sucedida do servidor, você expede uma ação de sucesso síncrona com os dados recebidos da resposta, mas para uma resposta de falha, envia-se uma ação síncrona diferente com a mensagem de erro.

    Ao usar uma API externa, como o JSONPlaceholder neste caso, é possível ver o atraso de rede real acontecendo. No entanto, se estiver trabalhando com um servidor de backend local, as respostas de rede podem acontecer muito rapidamente para visualizar o atraso de rede que um usuário real estaria observando. Sendo assim, é possível adicionar um atraso artificial ao desenvolver:

    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));
          });
      };
    };
    
    // ...
    

    Para testar cenários de erro, emita manualmente um erro:

    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));
          });
      };
    };
    
    // ...
    

    Para fins didáticos, aqui está um exemplo de como o redutor de tarefa pendente poderia ser para lidar com o ciclo de vida completo da solicitação:

    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;
      }
    }
    

    Explorando o getState

    Além de receber o método de expedição do estado, a função retornada por um criador de ação assíncrona com o Redux Thunk também recebe o método getState do armazenamento, de forma que os valores atuais do armazenamento possam ser lidos:

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

    Com o código acima, o estado atual será impresso no console.

    Por exemplo:

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

    Usar o getState pode ser útil para lidar com as coisas de maneira diferente dependendo do estado atual. Por exemplo, se quiser limitar o aplicativo a apenas quatro itens de tarefa por vez, você pode retornar da função se o estado já possuir a quantidade máxima de itens de tarefa:

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

    Com o código acima, o aplicativo ficará limitado a quatro itens de tarefa.

    Conclusão

    Neste tutorial, você explorou adicionar o Redux Thunk a um aplicativo React para permitir a expedição de ações de maneira assíncrona. Isso é útil ao usar um armazenamento Redux e APIs externas.

    Se quiser aprender mais sobre o React, dê uma olhada em nossa série Como programar no React.js, ou confira nossa página do tópico React para exercícios e projetos de programação.

    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!

    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