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.
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.
redux-thunk
Primeiro, use o terminal para navegar até o diretório do projeto e instale o pacote redux-thunk
em seu projeto:
- 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:
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.
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:
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
):
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:
// ...
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:
// ...
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:
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
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:
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:
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.
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.
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!