Como executar ações assíncronas do Redux usando Redux-Thunk

Saudações Habr! Apresento a você a tradução do artigo - Ações assíncronas de Redux usando Redux Thunk , autor - Alligator.io


Por padrão, as ações no Redux são síncronas, o que é um problema para um aplicativo que precisa interagir com a API do servidor ou executar outras ações assíncronas. Felizmente, o Redux nos fornece um middleware , que fica entre o despacho de ação e o redutor. Existem duas das bibliotecas de middleware mais populares para ações assíncronas no Redux, que são o Redux Thunk e o Redux Saga . Neste post, consideraremos o primeiro.

Redux Thunk é uma biblioteca de middleware que permite chamar o criador da ação, retornando uma função em vez de um objeto. A função usa o método de despacho como argumento, para que, após a conclusão da operação assíncrona, use-o para despachar a ação síncrona regular dentro do corpo da função.

Se você estiver interessado, o Thunk é um conceito no mundo da programação quando uma função é usada para atrasar uma operação.

Instalação e configuração


Primeiro, adicione o pacote redux-thunk ao seu projeto:

$ yarn add redux-thunk # ,   npm: $ npm install redux-thunk 

Em seguida, inclua o middleware ao criar o armazenamento do seu aplicativo usando o applyMiddleware fornecido pelo Redux:

index.js

 import React from 'react'; import ReactDOM from 'react-dom'; import { createStore, applyMiddleware } from 'redux'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers'; import App from './App'; //  applyMiddleware,   thunk middleware   const store = createStore(rootReducer, applyMiddleware(thunk)); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ); 

Uso principal


Normalmente, o Redux-Thunk é usado para solicitações assíncronas para uma API externa, para receber ou salvar dados. O Redux-Thunk facilita o envio de ações que seguem o "ciclo de vida" de uma solicitação de API externa.

Por exemplo, temos um aplicativo de tarefas regulares. Quando clicamos em "adicionar todo", normalmente, a primeira ação é despachada, que informa sobre o início da adição de um novo todo. Em seguida, se o elemento todo for criado e retornado com êxito pelo servidor, outra ação será despachada com nosso novo elemento todo e a operação será bem-sucedida. Se, por algum motivo, o servidor retornar um erro, em vez de adicionar um novo todo, será exibida uma ação com um erro informando que a operação não foi concluída.

Vamos ver como isso pode ser implementado usando o Redux-Thunk. No componente, a ação é despachada como de costume:

AddTodo.js

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

Na própria ação, a situação é muito mais interessante. Aqui usaremos a biblioteca Axios para solicitações de ajax. Se você não o tiver instalado, adicione-o assim:

 # Yarn $ yarn add axios # npm $ npm install axios --save 

Faremos uma solicitação POST para o endereço - jsonplaceholder.typicode.com/todos :
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 da ação addTodo retorna uma função, em vez da ação usual do objeto. Esta função recebe um argumento de despacho da loja.

Dentro do corpo da função, despachamos primeiro a ação síncrona usual, que relata que começamos a adicionar um novo todo usando uma API externa. Em palavras simples - a solicitação foi enviada ao servidor. Em seguida, fazemos uma solicitação POST ao servidor usando o Axios. No caso de uma resposta afirmativa do servidor, despachamos a ação sincronizada usando os dados recebidos do servidor. Porém, no caso de um erro do servidor, despachamos outra ação síncrona com uma mensagem de erro.

Quando usamos uma API realmente externa (remota), como o JSONPlaceholder no nosso caso, é fácil perceber que há um atraso até a resposta do servidor chegar. Mas se você estiver trabalhando com um servidor local, a resposta poderá ser rápida demais, para que você não note um atraso. Portanto, para sua conveniência, você pode adicionar atraso artificial ao desenvolver:

actions / index.js (parte do código)

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

E para testar um script com um erro, você pode lançar diretamente um erro:

actions / index.js (parte do código)

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

Por uma questão de integridade, aqui está um exemplo de como nosso redutor de tarefas pode parecer para processar o "ciclo de vida" completo de uma solicitação:

reducers / todoReducer.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


A função retornada pelo criador de ação assíncrona usando Redux-Thunk também aceita o método getState como o segundo argumento, o que permite obter o estado diretamente dentro do criador de ação:

actions / index.js (parte do código)

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

Ao executar esse código, o estado atual será simplesmente enviado ao console. Por exemplo:

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

O uso do getState pode ser realmente útil quando você precisa reagir de maneira diferente, dependendo do estado atual. Por exemplo, se limitarmos o número máximo de elementos todo a 4, podemos simplesmente sair da função se esse limite for excedido:

actions / index.js (parte do código)

 export const addTodo = ({ title, userId }) => { return (dispatch, getState) => { const { todos } = getState(); if (todos.length >= 4) return; dispatch(addTodoStarted()); // ... }; }; 
Curiosidade - você sabia que o código Redux-Thunk consiste em apenas 14 linhas? Você pode verificar por si mesmo como o middleware Redux-Thunk funciona sob o capô
Link para o artigo original - Ações assíncronas de Redux usando Redux Thunk .

Source: https://habr.com/ru/post/pt483314/


All Articles