Embora uma loja Redux possua excelentes recursos de gerenciamento de estado, ela não tem ideia de como lidar com a lógica assíncrona. Redux evita lidar com lógica assíncrona simplesmente porque não sabe o que você quer fazer com os dados que você buscou, muito menos se eles já foram buscados-olá, erros.
Middleware tem sido usado em aplicativos Redux para realizar tarefas assíncronas, com o middleware do Redux Thunk sendo o pacote mais popular. Um middleware é projetado para permitir que os desenvolvedores escrevam lógica que tem efeitos colaterais-que se refere a qualquer interação externa fora de um aplicativo cliente existente, como buscar dados de uma API.
Com o Redux Toolkit, o Redux Thunk é incluído por padrão, permitindo que createAsyncThunk execute lógica assíncrona atrasada antes de enviar o resultado processado para os redutores.
Neste artigo, você aprenderá a usar a API createAsyncThunk para executar tarefas assíncronas em aplicativos Redux.
Pré-requisitos
Você precisará ter algum conhecimento sobre Redux para entender o Redux Toolkit. No entanto, você pode consultar este post para aprender como criar aplicativos Redux com o Redux Toolkit .
Compreendendo os parâmetros da função
import {createSlice, createAsyncThunk} de’@ reduxjs/toolkit’const initialState={entity: [], loading: false,} const getPosts=createAsyncThunk (//tipo de ação string’posts/getPosts’,//callback function async (thunkAPI)=> {const res=await fetch (‘https://jsonplaceholder.typicode.com/posts’).then ((data)=> data.json ( )) return res}) export const postSlice=createSlice ({name:’posts’, initialState, reducers: {}, extraReducers: {},}) export const postReducer=postSlice.reducer
O arquivo acima é uma fatia Redux em um aplicativo React. Uma fatia é uma função que contém suas funções de armazenamento e redutor usadas para modificar os dados de armazenamento. A API createSlice é definida para ser a norma para escrever a lógica Redux.
No createSlice, as solicitações síncronas feitas para a loja são tratadas no objeto reducers enquanto extraReducers lida com as solicitações assíncronas, que é nosso foco principal.
As solicitações assíncronas criadas com createAsyncThunk aceitam três parâmetros: uma string de tipo de ação, uma função de retorno de chamada (referida como payloadCreator) e um objeto de opções.
Tomando o bloco de código anterior como um Redux store para um aplicativo de blog, vamos examinar getPosts:
const getPosts=createAsyncThunk (‘posts/getPosts’, async (thunkAPI)=> {const res=await fetch (‘https://jsonplaceholder.typicode.com/posts’).then ((data)=> data.json ()) return res})
posts/getPosts é a string de tipo de ação neste caso. Sempre que esta função é despachada de um componente dentro de nosso aplicativo, createAsyncThunk gera tipos de ação de ciclo de vida de promessa usando esta string como um prefixo:
pendente: posts/getPosts/pendente cumprido: posts/getPosts/preenchido rejeitado: posts/getPosts/rejeitado
Em sua chamada inicial, createAsyncThunk despacha o tipo de ação de ciclo de vida posts/getPosts/pendentes. O payloadCreator então executa para retornar um resultado ou um erro.
No caso de um erro, posts/getPosts/rejeitados são despachados e createAsyncThunk deve retornar uma promessa rejeitada contendo uma instância de Error, um descritivo simples mensagem ou uma promessa resolvida com um argumento RejectWithValue conforme retornado pela função thunkAPI.rejectWithValue (mais sobre thunkAPI e tratamento de erros momentaneamente).
Se nossa busca de dados for bem-sucedida, o tipo de ação posts/getPosts/preenchido é despachado.
O parâmetro options é um objeto que contém diferentes configurações para a API createAsyncThunk. Veja a lista de opções disponíveis .
Os três tipos de ação do ciclo de vida mencionados anteriormente podem em seguida, ser avaliados em extraReducers, onde fazemos nossas alterações desejadas para a loja. Nesse caso, vamos preencher as entidades com alguns dados e definir apropriadamente o estado de carregamento em cada tipo de ação:
import {createSlice, createAsyncThunk} from’@ reduxjs/toolkit’const initialState={entity: [], loading: false ,} const getPosts=createAsyncThunk (‘posts/getPosts’, async (thunkAPI)=> {const res=await fetch (‘https://jsonplaceholder.typicode.com/posts’).then ((dados)=> dados. json ()) return res}) export const postSlice=createSlice ({name:’posts’, initialState, reducers: {}, extraReducers: {[getPosts.pending]: (state)=> {state.loading=true}, [getPosts.fulfilled]: (state, {payload})=> {state.loading=false state.entities=payload}, [getPosts.rejected]: (state)=> {state.loading=false},},} ) export const postReducer=postSlice.reducer
Se você é novo no Redux Toolkit, a lógica de estado acima pode parecer estranha para você. O Redux Toolkit faz uso da biblioteca Immer, que permite aos desenvolvedores escrever lógica mutável em funções redutoras. Immer então converte sua lógica mutável em lógica imutável nos bastidores.
Além disso, observe a expressão da função. Para preferência pessoal, usei a notação de objeto de mapa para lidar com as solicitações, principalmente porque essa abordagem parece mais organizada.
A maneira recomendada de lidar com as solicitações é a notação de retorno de chamada do construtor porque essa abordagem tem melhor suporte para TypeScript (e, portanto, o preenchimento automático do IDE até mesmo para usuários de JavaScript).
NB: À medida que seu aplicativo cresce, você continuará a fazer solicitações mais assíncronas para sua API de back-end e, por sua vez, a lidar com seus tipos de ação do ciclo de vida. Consolidar toda essa lógica em um arquivo torna o arquivo mais difícil de ler. Escrevi um artigo sobre minha abordagem para separar a lógica em seus aplicativos do Redux Toolkit .
Despachando ações em componentes
Usando useSelector e useDispatch de react-redux, podemos ler o estado de um armazenamento Redux e despachar qualquer ação de um componente, respectivamente.
Vamos configurar um componente para despachar getPosts quando for montado:
import {useEffect} de’react’import {useDispatch, useSelector} de’react-redux’import {getPosts} de’../redux/features/posts/postThunk’exportar função padrão Home () {const dispatch=useDispatch () const {entity, loading}=useSelector ((state)=> state.posts) useEffect (()=> {dispatch (getPosts ( ))}, []) if (carregando) return
Carregando…
return (
Postagens do blog
{entity.map ((post)=> (
{post.title}
))}
)}
O extensão Redux DevTools fornece informações em tempo real sobre o envio de qualquer tipo de ação do ciclo de vida.
parâmetros aceitáveis .
Se sua solicitação requer mais do que um parâmetro, você pode passar um objeto ao despachar a função do redutor:
despachar (getPosts ({categoria:’política’, sortBy:’nome’})
Manipulação de erros em createAsyncThunk
Lembre-se de que quando seu payloadCreator retorna uma promessa rejeitada, a ação rejeitada é despachada (com action.payload como indefinido). Na maioria das vezes, queremos exibir mensagens de erro personalizadas em vez da mensagem retornada no objeto Error.
Ao usar thunkAPI, você pode retornar uma promessa resolvida ao redutor, que tem action.payload definido como um valor personalizado de sua escolha. thunkAPI usa sua propriedade rejeitarWithValue para fazer isso.
Digamos que nós deseja adicionar uma nova postagem ao blog. Nossa função createAsyncThunk seria mais ou menos assim:
const post={title:’lorem’, body:’ipsum’} const addPost=createAsy ncThunk (‘posts/addPost’, async (post, {rejeitarWithValue})=> {try {const response=await fetch (‘https://jsonplaceholder.typicode.com/posts’, {método:’POST’, corpo: JSON.stringify (post), header: {‘Content-Type’:’application/json’,},}) const data=await response.json () return data} catch (err) {//Você pode escolher usar a mensagem anexada para errar ou escrever um erro personalizado retornar rejeitarWithValue (‘Opps, parece haver um erro’)}})
Em seguida, avalie as postagens/addPost/rejeitadas em extraReducers:
extraReducers: {[addPost.rejected ]: (state, action)=> {//retorna’Opps, parece haver um erro’console.log (action.payload)}}
Chegamos ao fim aqui, devs. Até agora, pudemos examinar os recursos básicos do createAsyncThunk e ver como ele funciona com os redutores na função de fatia. A API também tem alguns tópicos mais avançados, como cancelamento de solicitações, que você pode ler mais adiante.
Conclusão
Para concluir, gostaria de mencionar o API de busca de dados RTK Query .
O RTK Query é uma solução de busca de dados e armazenamento em cache construída para fins específicos para aplicativos Redux, que podem eliminar a necessidade de escrever qualquer thunks ou redutores para gerenciar a busca de dados. Portanto, se você já se envolveu com uma biblioteca como React Query, seria sábio usar RTK Query para lógica assíncrona no Redux porque a sintaxe é bastante semelhante.