O gerenciamento de estado é um desafio fundamental que todo desenvolvedor enfrenta ao construir um aplicativo React-e não é trivial. Existem muitas maneiras válidas de gerenciar o estado no React, e cada uma resolve um conjunto importante de problemas.

Como desenvolvedores, é importante não apenas estar ciente das diferentes abordagens, ferramentas e padrões, mas também entender seus casos de uso e compensações.

Uma maneira útil de pensar sobre gerenciamento de estado é em termos dos problemas que resolvemos em nossos projetos. Neste artigo, vamos cobrir casos de uso comuns para gerenciar estado no React e aprender quando você deve considerar o uso de cada solução. Vamos conseguir isso criando um aplicativo de contador simples.

Estado do componente local no React

A maneira mais simples de implementar o contador é usar o estado do componente local com o gancho useState .

 import {useState} de'react' Contador const=()=> { const [contagem, setCount]=useState (0) const boostCount=()=> { setCount (contagem + 1) } const diminuiContagem=()=> { if (contagem> 0) { setCount (contagem-1) } } Retorna ( 

{contagem}

) } exportar contador padrão

Terminamos, certo? Artigo encerrado? Não exatamente.

Se este fosse um projeto real, é provável que, no futuro, precisaríamos de mais botões e cabeçalhos em outro lugar em nosso aplicativo. E é uma boa ideia garantir que todos eles tenham uma aparência e se comportem de maneira consistente, por isso provavelmente deveríamos transformá-los em componentes React reutilizáveis.

Componentes de adereços no React

Transformar nosso botão e Cabeçalho em componentes separados revela um novo desafio. Precisamos de alguma forma de comunicação entre eles e o componente principal do Contador .

É aqui que os adereços de componentes entram em jogo. Para nosso componente Cabeçalho , adicionamos um prop texto . Para nosso Button , precisamos de um prop label e um callback onClick . Nosso código agora se parece com este:

 import {useState} de'react' const Header=({text})=> 

{text}

Const Button=({label, onClick})=> ( ) Contador const=()=> { const [contagem, setCount]=useState (0) const boostCount=()=> { setCount (contagem + 1) } const diminuiContagem=()=> { if (contagem> 0) { setCount (contagem-1) } } Retorna (
) } exportar contador padrão

Isso parece ótimo! Mas imagine o seguinte cenário: e se precisarmos exibir apenas a contagem em nossa rota de origem e tiver uma rota separada /controles onde exibimos os botões de contagem e de controle? Como devemos fazer isso?

Roteamento em reação

Visto que estamos construindo um aplicativo de página única, agora há uma segunda parte do estado que precisamos tratar-a rota em que estamos. Vamos ver como isso pode ser feito com React Router , por exemplo.

 import {BrowserRouter as Router, Switch, Route, Link} de'react-router-dom'
import {useState} de'react' const Header=({text})=> 

{text}

Const Button=({label, onClick})=> ( ) const Home=({count})=> { return
} Controles const=({contagem, diminuiçãoContagem, aumentoContagem})=> { Retorna ( <>

Legal! Agora temos nossas rotas separadas e tudo funciona conforme o esperado. No entanto, você pode notar um problema. Estamos mantendo nosso estado de contagem no App e usando adereços para passá-lo para a árvore de componentes. Mas parece que passamos pelo mesmo suporte repetidamente até chegarmos ao componente que precisa usá-lo. Claro, conforme nosso aplicativo cresce, ele só vai piorar. Isso é conhecido como perfuração de suporte.

Vamos consertar!

Usando Context + useReducer

Não seria ótimo se houvesse uma maneira de nossos componentes acessarem o estado count sem ter que recebê-lo por meio de adereços? Uma combinação da API React Context e do gancho useReducer faz exatamente isso:

 import {BrowserRouter as Router, Switch, Route, Link} de'react-router-dom'
import {createContext, useContext, useReducer} de'react' const initialState=0 redutor const=(estado, ação)=> { switch (action.type) { case'INCREMENT': estado de retorno + 1 case'DECREMENT': estado de retorno-1>=0? estado-1: 0 padrão: estado de retorno }
} const CountContext=createContext (null) const useCount=()=> { valor const=useContext (CountContext) if (value===null) lança um novo erro ('CountProvider ausente') valor de retorno
} const CountProvider=({children})=> (  {crianças} 
) const Header=({text})=> 

{text}

Const Button=({label, onClick})=> ( ) const Home=()=> { const [estado]=useCount () return
} Controles const=()=> { const [estado, despacho]=useCount () Retorna ( <>

Incrível! Resolvemos o problema da perfuração da hélice. Recebemos pontos adicionais por termos tornado nosso código mais declarativo, criando um redutor descritivo.

Estamos felizes com nossa implementação e, para muitos casos de uso, é realmente tudo de que precisamos. Mas não seria ótimo se pudéssemos persistir a contagem para que ela não seja redefinida para 0 toda vez que atualizamos a página? E ter um log do estado do aplicativo? E quanto aos relatórios de falhas?

Seria muito útil saber o estado exato em que nosso aplicativo estava quando travou, e também como aproveitar as vantagens das incríveis ferramentas de desenvolvimento enquanto o fazemos. Bem, podemos fazer exatamente isso usando Redux!

Usando Redux para gerenciamento de estado

Podemos fazer tudo isso e muito mais usando Redux para gerenciar o estado de nosso aplicativo. A ferramenta tem uma forte comunidade por trás dela e um rico ecossistema que pode ser aproveitado com facilidade.

Vamos configurar nosso contador com Redux Toolkit .

 import {BrowserRouter as Router, Switch, Route, Link} de'react-router-dom'
import {configureStore, createSlice} de'@ reduxjs/toolkit'
import {useSelector, useDispatch, Provider} de'react-redux' const counterSlice=createSlice ({ nome:'contador', Estado inicial: { valor: 0, }, redutores: { incremento: estado=> { estado.valor +=1 }, decremento: estado=> { if (state.value> 0) { state.value-=1 } }, },
}) const store=configureStore ({ redutor: {counter: counterSlice.reducer},
}) const {incremento, decremento}=counterSlice.actions const Header=({text})=> 

{text}

Const Button=({label, onClick})=> ( ) const Home=()=> { const count=useSelector (state=> state.counter.value) return
} Controles const=()=> { const count=useSelector (state=> state.counter.value) const dispatch=useDispatch () Retorna ( <>

Isso parece muito legal! Nosso estado agora é armazenado na loja Redux global e gerenciado com funções puras (o Redux Toolkit usa Immer sob o capô para garantir a imutabilidade). Já podemos aproveitar as vantagens do incrível Redux DevTools .

Mas e quanto a coisas como lidar com efeitos colaterais, tornar o estado persistente ou implementar registro e/ou relatório de falhas? É aqui que o ecossistema Redux que mencionamos anteriormente entra em ação.

Existem várias opções para lidar com os efeitos colaterais, incluindo redux-thunk e redux-saga . Bibliotecas como redux-persist são ótimas para salvar os dados do armazenamento redux em armazenamento local ou de sessão para torná-lo persistente.

Resumindo, Redux é ótimo! É amplamente utilizado no mundo do React e por um bom motivo.

Mas e se preferirmos uma abordagem mais descentralizada para a gestão do estado? Talvez estejamos preocupados com o desempenho ou tenhamos atualizações de dados frequentes em diferentes ramos da árvore React, então queremos evitar re-renderizações desnecessárias enquanto mantemos tudo em sincronia.

Ou talvez precisemos de uma boa maneira de derivar dados de nosso estado e computá-los de forma eficiente e robusta no cliente. E se quisermos alcançar tudo isso sem sacrificar a capacidade de observação de estado em todo o aplicativo? Insira o Recoil.

Estado atômico com recuo

É um pouco exagerado sugerir que somos capazes de atingir os limites do React Context ou Redux com um simples aplicativo Counter. Para um caso de uso de gerenciamento de estado atômico melhor, confira o vídeo incrível de Dave McCabe em Recuo .

No entanto, pensar no estado em termos de átomos ajuda a expandir nosso vocabulário de como seria o gerenciamento de estado. Além disso, é divertido brincar com a API Recoil, então vamos reimplementar nosso contador com ela.

 import {BrowserRouter as Router, Switch, Route, Link} de'react-router-dom'
import {atom, useRecoilState, RecoilRoot} de'recuo' const countState=atom ({ chave:'contar', padrão: 0,
}) const Header=({text})=> 

{text}

Const Button=({label, onClick})=> ( ) const Home=()=> { const [count]=useRecoilState (countState) return
} Controles const=()=> { const [count, setCount]=useRecoilState (countState) const boostCount=()=> { setCount (contagem + 1) } const diminuiContagem=()=> { if (contagem> 0) { setCount (contagem-1) } } Retorna ( <>

Usar o Recoil é muito parecido com o próprio React. Uma olhada em nossos exemplos iniciais revela como os dois são semelhantes. O Recoil também tem seu próprio conjunto de ferramentas de desenvolvimento . Uma consideração importante a ter em mente é que esta biblioteca ainda é experimental e está sujeita a alterações. Use-o com cuidado.

Ok, podemos ter um contador de recuo. Mas as preferências de gestão do estado dependem de nossas prioridades. E se o aplicativo for criado por uma equipe e for realmente importante que o desenvolvedor, o designer, o gerente de projeto e todos os outros falem a mesma linguagem no que diz respeito a interfaces de usuário?

E se, além disso, essa linguagem pudesse ser expressa diretamente com código altamente declarativo em nosso aplicativo? E se pudéssemos garantir que nunca atingiremos estados impossíveis, eliminando assim toda uma classe de bugs? Adivinha? Nós podemos.

Máquinas de estado com XState

Todos os itens acima podem ser alcançados com a ajuda de gráficos de estado e máquinas de estado. Os gráficos de estado ajudam a visualizar todos os estados possíveis de nosso aplicativo e definir o que é possível. Eles são fáceis de entender, compartilhar e discutir com toda a equipe.

Aqui está nosso contador como um gráfico de estado:

O contador tem permissão para aumentar, é Não é possível que o contador diminua

Embora esta seja uma implementação trivial, já podemos ver uma vantagem interessante em usar máquinas de estado. Inicialmente, não é possível decrementar o contador, pois seu valor inicial é 0. Esta lógica é declarada certa em nossa máquina de estados e visível no gráfico, onde com outras abordagens que exploramos, foi mais difícil, de modo geral, encontrar o correto lugar para isso.

Aqui está nossa máquina de estado na prática:

 import {BrowserRouter as Router, Switch, Route, Link} de'react-router-dom'
import {useMachine} de'@ xstate/react'
import {createMachine, assign} de'xstate' export const counterMachine=createMachine ({ inicial:'ativo', contexto: {contagem: 0}, estados: { ativo: { sobre: ​​{ INCREMENTO: { ações: atribuir ({contagem: ctx=> ctx.count + 1}), }, DIMINUIÇÃO: { cond: ctx=> ctx.count> 0, ações: atribuir ({ contagem: ctx=> ctx.count-1, }), }, }, }, },
}) const Header=({text})=> 

{text}

Const Button=({label, onClick})=> ( ) const Home=()=> { const [estado]=useMachine (counterMachine) return
} Controles const=()=> { const [estado, enviar]=useMachine (counterMachine) Retorna ( <>

Uau, isso é realmente ótimo! No entanto, estamos apenas arranhando a superfície das máquinas de estado aqui. Para saber mais sobre eles, verifique os documentos para XState .

Tudo bem, último cenário! O que acontecerá se nosso aplicativo de contador de front-end simples tiver um back-end? E se precisarmos nos comunicar com um servidor para obter ou modificar a contagem? E se, além disso, quisermos lidar com desafios relacionados à busca de dados, como assincronicidade, estados de carregamento, armazenamento em cache e nova busca?

Busca de dados com consulta React

A ferramenta final de gerenciamento de estado do React que desejo destacar é React Query . Ele é projetado especificamente para facilitar a busca de dados e resolver os problemas descritos acima (e mais). Vamos ver em ação.

 import {BrowserRouter as Router, Switch, Route, Link} de'react-router-dom'
importar {ReactQueryDevtools} de'react-query/devtools'
importar axios de'axios'
import { useQuery, useMutation, QueryClient, QueryClientProvider,
} de'react-query' const useCount=()=> { return useQuery ('count', async ()=> { const {data}=await axios.get ('https://our-counter-api.com/count') dados de retorno })
} const useIncreaseCount=()=> { return useMutation (()=> axios.post ('https://our-counter-api.com/increase', { onSuccess: ()=> { queryClient.invalidateQueries ('count') }, }), )
} const useDecreaseCount=()=> { return useMutation ( ()=> axios.post ('https://our-counter-api.com/descrease'), { onSuccess: ()=> { queryClient.invalidateQueries ('count') }, }, )
}
const Header=({text})=> 

{text}

Const Button=({label, onClick})=> ( ) const Home=()=> { const {status, data, error}=useCount () status de retorno==='carregando'? ( 'Carregando...' ): status==='erro'? ( Erro: {error.message} ): ( ) } Controles const=()=> { const {status, data, error}=useCount () const boostCount=useIncreaseCount () const diminuiCount=useDecreaseCount () status de retorno==='carregando'? ( 'Carregando...' ): status==='erro'? ( Erro: {error.message} ): ( <>

O texto acima é uma implementação bastante ingênua com muito espaço para melhorias. O que é importante observar é a facilidade com que podemos fazer chamadas ao servidor, armazená-las em cache e invalidar o cache quando necessário. Além disso, com o React Query, a tarefa de gerenciar o carregamento e os estados de erro no componente se torna muito mais simples.

É uma ótima ferramenta que pode ser usada com qualquer back-end. Se você quiser saber como configurá-lo com GraphQL, consulte meu artigo sobre isso.

Conclusão

O gerenciamento de estado no React é um tópico extenso. A lista de abordagens, padrões e bibliotecas discutida neste artigo não é abrangente nem definitiva. O objetivo é ilustrar o processo de pensamento por trás da resolução de um problema específico de uma maneira particular.

No final, o que o gerenciamento de estado no React significa é estar ciente das diferentes opções, compreender seus benefícios e compensações e, por fim, encontrar a solução que melhor se encaixa em nosso caso de uso.

Boa programação! ✨

A postagem Como escolher o certo A solução de gerenciamento de estado React apareceu primeiro no LogRocket Blog .