Gerenciamento de Estado no React: do Redux ao Zustand

Ítalo Assunção
Geekie Educação
Published in
11 min readApr 16, 2024
Exemplo do fluxo de estado padrão do Redux, com Actions, Reducers, Stores e Views
Fonte: fe-tool.com

Sempre que estamos desenvolvendo uma aplicação React chegamos em um ponto onde é necessário que um pedaço de estado (por exemplo, informações de produtos, formulários etc.) sejam consumidos e modificados por diversas partes do código, de forma que passar essa informação por “prop drilling”, além de nada elegante, acaba sendo uma dor de cabeça a mais na hora de desenvolver ou realizar manutenção.

Essa dor, experienciada por todas as pessoas que desenvolveram em React em algum momento, incentivou fortemente a criação de bibliotecas de gerenciamento de estado. Nesse artigo, vamos entender um pouco mais sobre a evolução dessas ferramentas e suas propostas e diferenças.

O Problema do Paradigma MVC

O MVC (Model-View-Container), um dos mais difundidos paradigmas do desenvolvimento web, embora muito útil e usado com outras linguagens e frameworks, no ambiente React não fazia tanto sentido. O problema do MVC com o React tinha a ver com o fato do fluxo de dados no React não ser linear: uma View poderia disparar uma ação que alteraria o Model de outra View e assim por diante, sendo que a maioria poderia estar sendo exibida e computada ao mesmo tempo no mesmo lugar.

Um exemplo da arquitetura MVC (Model, View, Controller) com uma Action apontando para um Controler que aponta para 7 Models que apontam, aleatoriamente, para varias Views que também apontam de volta para alguns modelos, de forma desorganizada
Fonte: Flux In-Depth Overview

Essas relações em excesso geravam uma árvore muito grande de interdependências e, para lidar com ela, era necessário buscar os dados (como os estados da aplicação) em um nó próximo da raiz e sair passando para todos os componentes ao longo dos galhos da árvore, até que os componentes que realmente precisavam do dado o recebessem. Essa prática poderia, inclusive, causar diversos re-renders indesejados.

A Arquitetura Flux

Compreendendo a dor dos excessivos “prop dillings” necessários para grandes aplicações React, o Facebook (atual Meta e criador e mantenedor oficial do React) criou a arquitetura Flux como solução para esse problema.

Essa arquitetura propunha um fluxo de dados linear. Para isso, foram desenhados 4 componentes principais:

  • Actions: representam eventos que acontecem na aplicação, como interações com o usuário ou até em resposta a outros eventos.
  • Dispatcher: é um orquestrador das Actions, recebe todas as Actions disparadas pela aplicação e simplesmente as redireciona para as Stores interessadas.
  • Store: local onde estão armazenados o estado da aplicação, a regra de negócio e a lógica de atualização do estado. Quando uma Action chega na Store ela é processada e, então, é emitido um evento para que todas as Views que se interessam possam receber o estado atualizado.
  • Views: basicamente são os componentes React, que vão receber os estados (e suas atualizações) diretamente da Store e, quando necessário, enviar Actions para o Dispatcher.
Proposta da arquitetura flux com uma Action desconectada sendo enviada para o Dispatcher, que repassa para a Store e essa para a View, que manda outra Action para o Dispatcher e o fluxo segue linearmente.
Fonte: Flux In-Depth Overview

Dessa forma, o fluxo de dados da aplicação ficaria muito mais previsível e fácil de entender.

Redux: O “Marco Zero”

Pautado completamente na arquitetura Flux nasce o Redux: a biblioteca de gerenciamento de estado mais amplamente usada (com bastante folga para o segundo lugar) do React.

O Redux implementa basicamente a emissão de eventos do Dispatcher, de forma que ainda é necessário criar a Store, as Actions e a camada de lógica de atualização de estado, chamada de Reducer. Os códigos abaixo mostram um setup extremamente simples com o Redux antigo (spoiler: foi deprecado e tem substituto oficial):

*É necessário baixar as bibliotecas redux e react-redux além do setup normal do React para rodar os exemplos abaixo

Nesse setup, tudo começa nos Reducers. Um Reducer nada mais é do que uma função que recebe o estado da aplicação e a Action disparada e, baseado no tipo da Action (action.type), altera o estado de acordo (essa Action é apenas um objeto normal, então pode conter outras propriedades que se façam necessárias para as alterações de estado).

Depois de definir o Reducer (ou Reducers, que provavelmente será o necessário numa aplicação maior), precisamos criar a Store. Aqui recebemos uma mensagem avisando que o createStore está deprecado, mas fica comigo que a gente vai ver como fazer da forma atual já já!

Também aplicamos o Provider do react-redux, que é o responsável por permitir que os nossos componentes internos da árvore acessem a Store.

Por último, usamos o useSelector para ter acesso ao estado que criamos e o useDispatch para podermos disparar as Actions que criamos. Dessa forma, quando clicarmos no botão Increment, vamos disparar a Action INCREMENT_COUNTER que, ao chegar no Reducer, vai somar 1 ao valor do estado counter e, quando clicarmos no botão Decrement, vamos disparar a ActionDECREMENT_COUNTER que, também ao chegar no Reducer, vai subtrair 1.

Note que o valor da variável count já é atualizado automaticamente ao chamarmos as Actions, isso porque quando um Reducer atualiza um estado, ele envia um evento para a Store para que todas as Views inscritas no determinado estado sejam atualizadas.

Considerações

Embora seja, disparada, a biblioteca de gerenciamento de estado mais baixada (claro que muito por conta dos projetos legado) entre as outras que veremos ainda nesse artigo, essa versão do Redux já é considerada deprecada, não sendo recomendada mais.

Ela solucionou o problema inicial implementando a arquitetura Flux, mas trouxe consigo outros problemas:

  • Tem uma curva de aprendizado íngreme;
  • Código boilerplate excessivo, para cada novo estado é necessário criar Actions e Reducers específicos;
  • Problemas de performance: por conta de todo o estado ficar num mesmo lugar, dependendo da implementação e tamanho da aplicação, os re-renders das Views inscritas nesse estado podem ser excessivos (e se realmente for uma aplicação grande, bem custosos e acabar baixando a performance).

React Context: A Solução(?) Nativa

Spoiler: não é solução para os problemas de gerenciamento de estado!

A grande diferença, e o motivo pelo qual não vou me aprofundar muito nisso, é que o propósito dela não é gerenciar estado, apenas tornar mais fácil acessar estados de várias partes do código sem precisar correr para o prop drilling, como dito na própria documentação. Por conta disso, não cabe discutir e comparar ele às outras soluções.

Qualquer implementação do React Context unida ao useState vai funcionar similarmente ao gerenciamento de estado, mas não é o indicado, não é o objetivo para o qual essa API foi criada e, inclusive, é bastante propenso a re-renders.

A recomendação é sempre usar com cautela, se realmente for necessário, tentar separar os contextos ao máximo para evitar re-renders e ponderar com calma se outra solução real de gerenciamento de estado não deveria ser usada no lugar.

Redux-Toolkit: Novo, Melhor, Mais Elegante

O Redux-Toolkit, ou “RTK”, é a forma mais atual (desde 2019) a implementar o Redux, de acordo com a própria documentação dele. Foi criado para resolver os dois primeiros problemas citados acima: ser complicado demais e ter muito código boilerplate.

É, basicamente, uma nova API cheia de utilitários para melhorar a experiência de desenvolvimento da tecnologia. Vamos ver abaixo como fica o código com essa nova implementação:

*É necessário baixar as bibliotecas @reduxjs/toolkit e react-redux além do setup normal do React para rodar os exemplos abaixo

O RTK agora introduz o conceito de Slice. Um Slice é como uma “fatia” do estado, que tem seus Reducers, valores e estados iniciais totalmente escopados e separados dos demais. Se você precisasse, por exemplo, lidar com estado de um usuário, poderia criar um arquivo userSlice.js com um código parecido, mudando o name, initialState e os Reducers. Dessa forma, não é mais necessário verificar qual é o action.type e toda a lógica referente ao seu estado estará exatamente no mesmo lugar. Muito mais limpo e elegante, não é?

Agora, ao invés de chamarmos a função createStore que estava deprecada, o RTK nos fornece a função configureStore que recebe um objeto com a prop reducer que é um outro objeto onde as chaves são os name dos Slices e os valores são os slice.reducer.

E por fim, ao invés de dispararmos uma Action que é um objeto com um type e outras propriedades se necessário, agora chamamos as Actions diretamente do Slice, passando parâmetros se necessário. E com isso não temos que nos preocupar mais com os types das Actions, os estados estão mais isolados e temos mais facilidade para incrementar/dar manutenção em toda a árvore de estados.

Considerações

O RTK tem um ar bem mais novo e é uma experiência consideravelmente melhor usar ele do que o Redux original, principalmente em codebases maiores e ajuda com a curva de aprendizado no geral.

Recoil: A Promessa Inspiradora

O Recoil é uma biblioteca experimental desenvolvida pelo Facebook (Meta) que traz uma proposta bastante diferente da arquitetura Flux proposta por eles mesmo (vai entender…). Nela, todo estado é um Átomo, que por sua vez é um objeto composto por uma key única por toda a aplicação e um valor padrão. Além dessa estrutura nova, o Recoil fornece hooks para pegar e atualizar esses estados de forma global na aplicação.

*É necessário baixar apenas a biblioteca recoil além do setup normal do React para rodar os exemplos abaixo

Aqui temos um Atom. Simples assim. Não precisamos de Actions, Reducers, Models, Controllers, nada! Basta criar o objeto atom e exportá-lo.

De novo, a escolha da biblioteca é a simplicidade: não precisamos criar uma Store nem nada do tipo. Apenas chamar o RecoilRoot e ele automaticamente sabe o que fazer!

E por fim, aqui está a grande inovação do Recoil: mudança de estado global da aplicação e consumo dele de forma simples e fácil, exatamente como quando usamos o nativo useState!

O grande problema do Recoil é… que ele foi abandonado 😓. Embora exista desde o inicio de 2020, até hoje não alcançou a versão 1.0 estável. Além disso, a última atualização já faz quase um ano e o principal mantenedor foi dispensado em um dos layoffs da empresa, então a comunidade no geral não tem muitas esperanças da biblioteca voltar a vida…

Considerações

Como não está mais sendo ativamente continuado, não é uma boa escolha para novos projetos e seu futuro é incerto.

Contudo, o Recoil trouxe inspiração para diversas outras bibliotecas, provando que a simplicidade ideal podia ser alcançada, além ser uma solução para os possíveis (embora raros) problemas de performance do Redux, isolando os estados em átomos.

Jotai: O Filho Brilhante

Inspirado pelas diversas inovações do Recoil nasce o Jotai. O conceito principal do Jotai é o gerenciamento de estado a partir de átomos, também tendo uma sintaxe extremamente similar ao useState do React.

Além dessa funcionalidade (que é só o que vou mostrar aqui), o Jotai também conta com diversas utilidades como trabalhar com persistência no localStorage e async de forma nativa e fácil de incrementar no código, além de várias extensões para atender aos requisitos complexos de uma aplicação de grande escala.

*É necessário baixar apenas a biblioteca jotai além do setup normal do React para rodar os exemplos abaixo

Similar a criação de um átomo no Recoil, mas ainda mais simples! No Jotai não é necessário criar uma key única, o próprio átomo cuida disso! O único dado necessário é o valor inicial.

E é assim que fica o nosso entrypoint. Espera… 😱 sem Provider? É! O jotai simplesmente não precisa de um provider nem nada do tipo, ele apenas sabe o que fazer assim que recebe um átomo nos seus hooks.

E, por fim, o nosso componente fica praticamente idêntico ao do Recoil, apenas mudando o nome do hook agora para useAtom.

É com essa simplicidade que o Jotai se torna uma das bibliotecas mais convidativas para adotar numa codebase, já que é basicamente um drop-in replacement para o nativo useState, além de trazer muita performance e facilidade na hora de editar e mapear o estado da aplicação!

Considerações

O Jotai é uma ferramenta bastante interessante, revigorante e fácil de ser implementada, ao mesmo tempo ainda sendo suficiente para usos complexos do gerenciamento de estado.

Um ponto fraco, para algumas pessoas, é que, assim como no useState, a função que altera o estado é genérica e seu funcionamento depende exclusivamente de quem a chama. Isso é uma consequência da facilidade de implementação do código base mas de modo geral não afeta a qualidade de código.

Para quem gostava do Recoil, fica a recomendação!

Zustand: O Redux 2.0

Também muito embasado na arquitetura Flux, mas tentando ser ainda mais simples de usar do que o Redux Toolkit, temos o Zustand. Com seu mascote bonitinho e usabilidade maravilhosa, a biblioteca entrega um gerenciamento de estado eficiente, fácil de dar manutenção e conceitualmente similar ao Redux, o que o torna ainda mais fácil de compreender para pessoas que já vem desse contexto.

Além do básico do gerenciamento de estado, o Zustand traz uma utilidade fantástica e inovadora (que também não vou entrar em detalhe aqui, se não o artigo não termina 😅) que são os transient updates. Essa feature é basicamente uma forma manual de escutar as alterações de estado, sem que elas causem um render de fato!!

*É necessário baixar apenas a biblioteca zustand além do setup normal do React para rodar os exemplos abaixo:

Como eu disse, o Zustand tem similaridades com o Redux nos conceitos da arquitetura Flux. Portanto, claro que começaríamos criando uma Store! Contudo, essa Store é bem mais básica do que as do Redux e Redux-Toolkit, sendo apenas um objeto onde as chaves com valores não-funcionais são os estados, e as chaves com valores funcionais podem usar a função set para atualizar o estado.

Assim como no Jotai, no Zustand não é necessário encapsular seus componentes com nenhum provider nem nada do tipo (essas bibliotecas modernas estão com tudo!!)

Por fim, basta pegarmos o estado e as Actions diretamente da store que criamos e usá-los! Consegue ser ainda mais simples do que o RTK, né?

Considerações

Além de buscar simplicidade na hora de ser implementado, o Zustand continua sendo capaz de entregar funcionalidades necessárias para grandes aplicações.

Vejo o Zustand como a nova encarnação do Redux, quem era muito fã provavelmente gostará desse também e, pela facilidade de uso, eventualmente deve ser mais popular ainda. Claro que desbancar o pai vai ser uma tarefa bem difícil 😅.

Legal, mas e agora… qual eu escolho? 😨

Tá! Falamos de várias ferramentas aqui, desde os primórdios da arquitetura que rege a maioria, mas não chegamos a uma conclusão 🤔…

A resposta que toda pessoa desenvolvedora dá, mas ao mesmo tempo odeia receber é: “não existe escolha certa”. Nenhuma dessas bibliotecas é uma bala de prata e absolutamente nada garante que amanhã não vai surgir uma outra melhor ainda que vai roubar a cena.

Se tratando de gerenciamento de estado, o uso pode ser extremamente diferente de aplicação para aplicação, bem como o costume e maneirismos de cada dev.

Mesmo assim, a minha regra pessoal é sempre (que possível, é claro, vamos evitar quebrar apps em prod xD) testar ferramentas mais atuais porque na maioria das vezes elas entregam funcionalidades iguais, se não mais ainda, com uma developer experience mais aprimorada, já que com certeza quem criou a ferramenta usou sua precedente e pensou em melhorias para si mesmo.

Ou seja… para novos apps eu recomendaria testar o Jotai e o Zustand e ver com qual você se encaixa melhor, mas mesmo assim, jamais abrir mão dos Redux e qualquer outra ferramenta, já que cada uma serve seu propósito, tem sua comunidade e funcionam de jeitos diferentes.

Até mais, e obrigado pelos peixes 👋

Enquete: deixe nos comentários qual é a sua biblioteca de gerenciamento de estado favorita! Mudou depois de ler esse artigo? 👀

Se tiver qualquer dúvida, sugestão ou comentário, sinta-se livre e encorajado a deixar aqui embaixo também!!

--

--

Ítalo Assunção
Geekie Educação

Brazilian Software Engineer @ Geekie. Basketball nerd and tech lover 💜