Tutorial

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

Published on December 2, 2020
    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 our products

    About the author(s)

    Category:
    Tutorial
    Tags:

    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!

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

    Please complete your information!

    Become a contributor for community

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

    DigitalOcean Documentation

    Full documentation for every DigitalOcean product.

    Resources for startups and SMBs

    The Wave has everything you need to know about building a business, from raising funding to marketing your product.

    Get our newsletter

    Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

    New accounts only. By submitting your email you agree to our Privacy Policy

    The developer cloud

    Scale up as you grow — whether you're running one virtual machine or ten thousand.

    Get started for free

    Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

    *This promotional offer applies to new accounts only.