React: Gerenciamento de estado: Zustand / Redux
Última atualização: 2026-08-26
O recurso de carrinho de compras do Tom foi inicialmente implementado passando dados camada por camada usando
useStateeprops, o que mal funcionava. No entanto, à medida que a árvore de componentes se tornava mais profunda (App → Header → CartIcon, App → ProductList → ProductCard → AddToCartButton), modificar o estado do carrinho de compras exigia passar funções de retorno de chamada por cinco camadas de componentes — e até mesmo o menor erro poderia resultar na perda ou na transmissão incorreta de um retorno de chamada. Ele percebeu: Era necessária uma solução de gerenciamento de estado global, permitindo que qualquer componente lesse e gravasse diretamente o estado compartilhado.
1. O que você vai aprender
- Zustand: A API principal para gerenciamento minimalista de estados
- Fluxo de trabalho do Redux Toolkit: Slice → Store → Provider → Hooks
- Critérios de seleção e cenários aplicáveis para as duas soluções
- Uso de middleware de armazenamento persistente
- Gerenciamento de estado com segurança de tipos no TypeScript
2. Diagramas conceituais
flowchart LR
subgraph Zustand[Zustand Pattern]
A1[create API] --> B1[Store<br/>state + actions]
B1 --> C1[Component A<br/>Subscribe Now]
B1 --> C2[Component B<br/>Subscribe Now]
B1 --> C3[Component C<br/>Subscribe Now]
end
subgraph Redux[Redux Toolkit Pattern]
A2[createSlice] --> B2[Slice<br/>reducers + actions]
B2 --> C4[configureStore]
C4 --> D[Provider<br/>Wrapper Root Component]
D --> E1[useSelector<br/>Read Status]
D --> E2[useDispatch<br/>Trigger an update]
end
style A1 fill:#e1f5fe,stroke:#0288d1
style B1 fill:#fff3e0,stroke:#f57c00
style C4 fill:#e8f5e9,stroke:#388e3c
style D fill:#c8e6c9,stroke:#2e7d32
Esquerda: Zustand — sem provedor, assinaturas diretas e uma API minimalista. Direita: Redux Toolkit — oferece um fluxo de trabalho completo e é adequado para a colaboração entre equipes grandes.
3. Um cenário da vida real
O carrinho de compras do Tom precisa de vários recursos: um botão “Adicionar ao carrinho” na página da lista de produtos, um ícone de carrinho de compras no cabeçalho (exibindo o número de itens), uma página do carrinho de compras (exibindo a lista de produtos e o preço total) e a possibilidade de esvaziar o carrinho após finalizar a compra. Esses componentes estão distribuídos por diferentes níveis da árvore de componentes, e passá-los usando useState + props não só é complicado quanto propenso a erros.
(1) Por que o Context não pode ser usado como substituto?
O Context do React realmente impede que os props sejam passados por vários níveis, mas apresenta dois problemas graves:
- Problema de desempenho: Quando um contexto é atualizado, todos os componentes que utilizam esse contexto são renderizados novamente, mesmo que sejam somente de leitura. Em um cenário de carrinho de compras, o ícone do carrinho também será renderizado novamente quando a quantidade for alterada.
- Ausência de mecanismo de ação integrado: O Context oferece apenas uma maneira padronizada de passar valores; ele não oferece uma maneira padronizada de atualizar o estado, o que facilita que a lógica de atualização fique espalhada pelo código.
Bibliotecas dedicadas ao gerenciamento de estado (Zustand, Redux Toolkit) resolvem problemas de desempenho por meio de um mecanismo de assinatura seletiva — os componentes apenas monitoram os fragmentos de estado que lhes dizem respeito, e atualizações irrelevantes não acionam uma nova renderização.
| Solução | Desempenho | Provedor | Assinatura seletiva | Curva de aprendizado | Tamanho do pacote |
|---|---|---|---|---|---|
| useState + props | ✅ Rápido em ambiente local | Nenhum | N/A | Muito baixo | 0 |
| Contexto + useReducer | ❌ Renderização completa | Obrigatório | ❌ | Baixo | 0 |
| Status | ✅ Assinatura opcional | Não obrigatória | ✅ Nativa | Baixa | ~1 KB |
| Redux Toolkit | ✅ useSelector | Obrigatório | ✅ | em | ~12 KB |
(2) Estado: Abordagem minimalista
A ideia central por trás do Zustand é um único hook que faz tudo: create() Ele define tanto o estado quanto as ações ao mesmo tempo, e o hook retornado pode ser usado diretamente em qualquer componente, sem a necessidade de ser encapsulado em um Provider.
npm install zustand
▶ Exemplo 1: Loja “Zustand Shopping Cart”
import { create } from 'zustand'
// Definition store Types of(TypeScript Project Recommendations)
/*
type CartItem = { id: number; name: string; price: number; qty: number }
type CartStore = {
items: CartItem[]
total: number
addItem: (product: Omit<CartItem, 'qty'>) => void
removeItem: (id: number) => void
updateQty: (id: number, qty: number) => void
clearCart: () => void
}
*/
// Create store — state and actions They're all defined here
const useCartStore = create((set, get) => ({
// --- state ---
items: [],
total: 0,
// --- actions ---
addItem: (product) => set((state) => {
const existing = state.items.find(i => i.id === product.id)
const newItems = existing
? state.items.map(i =>
i.id === product.id ? { ...i, qty: i.qty + 1 } : i
)
: [...state.items, { ...product, qty: 1 }]
return {
items: newItems,
total: newItems.reduce((sum, i) => sum + i.price * i.qty, 0)
}
}),
removeItem: (id) => set((state) => {
const newItems = state.items.filter(i => i.id !== id)
return {
items: newItems,
total: newItems.reduce((sum, i) => sum + i.price * i.qty, 0)
}
}),
updateQty: (id, qty) => set((state) => {
const newItems = qty <= 0
? state.items.filter(i => i.id !== id)
: state.items.map(i => i.id === id ? { ...i, qty } : i)
return {
items: newItems,
total: newItems.reduce((sum, i) => sum + i.price * i.qty, 0)
}
}),
clearCart: () => set({ items: [], total: 0 })
}))
// Use in any component
function CartIcon() {
// Subscribe to items.length only,The page will only be re-rendered when the shopping cart list changes.
const itemCount = useCartStore(state => state.items.length)
return <span className="cart-badge">Shopping Cart ({itemCount})</span>
}
function ProductCard({ product }) {
const addItem = useCartStore(state => state.addItem)
return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => addItem(product)}>Add to Cart</button>
</div>
)
}
function CartPage() {
const { items, total, updateQty, removeItem, clearCart } = useCartStore()
if (items.length === 0) return <p>Your shopping cart is empty</p>
return (
<div>
<h2>Shopping Cart</h2>
{items.map(item => (
<div key={item.id}>
<span>{item.name}</span>
<input
type="number"
value={item.qty}
min="0"
onChange={e => updateQty(item.id, Number(e.target.value))}
/>
<span>${item.price * item.qty}</span>
<button onClick={() => removeItem(item.id)}>Delete</button>
</div>
))}
<hr />
<p><strong>Total:${total}</strong></p>
<button onClick={clearCart}>Empty Cart</button>
</div>
)
}
Principais recursos do Zustand:
- Zero Provider: Não é necessário envolver
<Provider>; basta chamar o hook diretamente em qualquer componente - Assinatura seletiva de filhos:
useCartStore(state => state.items.length)aciona uma nova renderização somente quandoitems.lengthmuda, oferecendo melhor desempenho do que o Context - set + get:
setatualiza o estado;getlê o estado atual (útil para ler outros valores de estado dentro de uma ação)
▶ Exemplo 2: Middleware de persistência de estado
Tom quer que os usuários possam atualizar a página sem perder os dados do carrinho de compras. O middleware persist do Zustand pode sincronizar automaticamente a loja com o localStorage.
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
// persist Middleware Package store Definition
const useCartStore = create(
persist(
(set, get) => ({
items: [],
total: 0,
addItem: (product) => set((state) => {
const existing = state.items.find(i => i.id === product.id)
const newItems = existing
? state.items.map(i =>
i.id === product.id ? { ...i, qty: i.qty + 1 } : i
)
: [...state.items, { ...product, qty: 1 }]
return {
items: newItems,
total: newItems.reduce((sum, i) => sum + i.price * i.qty, 0)
}
}),
removeItem: (id) => set((state) => {
const newItems = state.items.filter(i => i.id !== id)
return {
items: newItems,
total: newItems.reduce((sum, i) => sum + i.price * i.qty, 0)
}
}),
clearCart: () => set({ items: [], total: 0 })
}),
{
name: 'cart-storage', // localStorage 's key
// Store all fields by default,That's fine, too. partialize Select which fields to store
// partialize: (state) => ({ items: state.items }),
}
)
)
// The way it works remains exactly the same
function CartIcon() {
const itemCount = useCartStore(state => state.items.length)
return <span>Shopping Cart ({itemCount})</span>
}
// localStorage Data Format Stored in the System:
// key: 'cart-storage'
// value: '{"state":{"items":[...],"total":99},"version":0}'
Como funciona o Persistence Middleware: Quaisquer alterações no estado são sincronizadas automaticamente com o localStorage, e os dados são restaurados automaticamente a partir do localStorage quando o aplicativo é iniciado. Quando o usuário atualiza a página, o estado do carrinho de compras é totalmente preservado.
▶ Exemplo 3: Depuração no Zustand DevTools
Durante a fase de desenvolvimento, Tom queria visualizar o histórico das mudanças de estado, de forma semelhante ao Redux DevTools. O middleware devtools do Zustand se integra perfeitamente às extensões do DevTools do navegador.
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
// Using Multiple Middleware Components Simultaneously:Combine using a nested approach
const useStore = create(
devtools(
persist(
(set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
}),
{ name: 'app-storage' }
),
{ name: 'AppStore' } // DevTools As shown in store Name
)
)
// Open in a browser Redux DevTools Expand
// As you can see "AppStore" All items under this tag state Change Log
// Supports time-travel debugging(Jump to any historical state)
Características do sistema de middleware Zustand: O middleware é executado sequencialmente, da camada externa para a camada interna, por meio de combinações de funções aninhadas. devtools(persist(...)) indica que o DevTools é conectado primeiro, seguido pelo processamento de persistência. Vários componentes de middleware podem ser combinados de forma flexível para atender a diferentes necessidades. Por exemplo, você pode adicionar o middleware immer para habilitar a sintaxe de variáveis ou criar um middleware personalizado para registrar cada ação.
4. Redux Toolkit: uma solução de nível empresarial
Quando um projeto cresce a ponto de envolver a colaboração de várias equipes, a flexibilidade do Zustand pode, na verdade, se tornar um problema — os desenvolvedores podem escrever ações em estilos inconsistentes. O Redux Toolkit oferece um fluxo de trabalho padronizado para gerenciamento de estado que garante um padrão consistente em toda a equipe.
npm install @reduxjs/toolkit react-redux
▶ Exemplo 4: O fluxo de trabalho completo do Redux Toolkit
import { createSlice, configureStore } from '@reduxjs/toolkit'
import { Provider, useSelector, useDispatch } from 'react-redux'
// ========== 1. Create Slice(Definition state + reducers)==========
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0, history: [] },
reducers: {
increment: (state) => {
state.value += 1
state.history.push(`+1 → ${state.value}`)
},
decrement: (state) => {
state.value -= 1
state.history.push(`-1 → ${state.value}`)
},
incrementByAmount: (state, action) => {
state.value += action.payload
state.history.push(`+${action.payload} → ${state.value}`)
},
reset: (state) => {
state.value = 0
state.history = []
}
}
})
// Automatically generated action creators
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions
// ========== 2. Layout Store ==========
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
// More can be added later. slice:
// cart: cartSlice.reducer,
// user: userSlice.reducer,
}
})
// ========== 3. Provider Inject ==========
function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
)
}
// ========== 4. Used in components ==========
function Counter() {
// Selective Reading of Characters
const value = useSelector(state => state.counter.value)
const history = useSelector(state => state.counter.history)
const dispatch = useDispatch()
return (
<div>
<h2>Counter:{value}</h2>
<div>
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(decrement())}>-1</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
<button onClick={() => dispatch(reset())}>Reset</button>
</div>
<div>
<h3>Transaction History</h3>
<ul>
{history.slice(-5).map((h, i) => (
<li key={i}>{h}</li>
))}
</ul>
</div>
</div>
)
}
Conceitos-chave do Redux Toolkit:
| Conceito | Função | Analogia |
|---|---|---|
createSlice |
Criar um conjunto de estados e redutores relacionados | Gaveta de arquivos: Organizar e armazenar |
configureStore |
Combinar todas as fatias para criar um armazenamento global | Arquivo: Gerenciamento centralizado |
Provider |
Injetando o store na árvore de componentes do React | Cabo de alimentação: conecte-o |
useSelector |
Ler um fragmento de estado do armazenamento | Abrir a gaveta para recuperar um arquivo |
useDispatch |
Acionar uma ação para atualizar o status | Emitir um comando para que o administrador tome uma medida |
▶ Exemplo 5: Operações assíncronas no Redux Toolkit
As atualizações de estado em projetos reais geralmente envolvem lógica assíncrona (como fazer login ou recuperar informações do usuário). O Redux Toolkit inclui suporte integrado para as etapas do ciclo de vida das operações assíncronas (pendente, concluída e rejeitada).
import { createSlice, configureStore, createAsyncThunk } from '@reduxjs/toolkit'
import { Provider, useSelector, useDispatch } from 'react-redux'
// 1. Defining Asynchronous thunk(Automatically Generated pending/fulfilled/rejected Three types action)
const fetchUser = createAsyncThunk(
'user/fetchUser', // action Type prefix
async (userId, { rejectWithValue }) => {
try {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) throw new Error('Failed to retrieve the user')
return await response.json()
} catch (err) {
return rejectWithValue(err.message)
}
}
)
// 2. Create slice
const userSlice = createSlice({
name: 'user',
initialState: {
data: null,
loading: false,
error: null
},
reducers: {
clearUser: (state) => {
state.data = null
state.error = null
}
},
// Handling Asynchronous Operations thunk The Three States of
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true
state.error = null
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false
state.data = action.payload
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false
state.error = action.payload
})
}
})
export const { clearUser } = userSlice.actions
// 3. Layout store
const store = configureStore({
reducer: { user: userSlice.reducer }
})
// 4. Used in components
function UserProfile({ userId }) {
const dispatch = useDispatch()
const { data, loading, error } = useSelector(state => state.user)
return (
<div>
<button onClick={() => dispatch(fetchUser(userId))}>Load Users</button>
{loading && <p>Loading......</p>}
{error && <p>Error:{error}</p>}
{data && <p>{data.name} — {data.email}</p>}
</div>
)
}
O createAsyncThunk lida automaticamente com os três estados: Quando uma solicitação é iniciada, ele despacha pending → Em caso de sucesso, ele despacha fulfilled (contendo os dados) → Em caso de falha, ele despacha rejected (contendo a mensagem de erro). Os desenvolvedores precisam apenas definir como o estado muda para cada condição dentro de extraReducers. Em comparação com o uso direto de useEffect e fetch, as vantagens desse padrão incluem: rastreamento automático do status da solicitação, ações que podem ser registradas pelo DevTools e a capacidade de vários componentes se inscreverem para receber os resultados do mesmo thunk.
5. Comparação de modelos
| Dimensão | Estado | Redux Toolkit |
|---|---|---|
| Volume de código | Muito pequeno — apenas uma função create() |
Moderado — o conjunto completo de funções slice, store e Provider |
| Curva de aprendizado | Baixa (10 minutos para começar) | Média (requer compreensão de redutores, ações, despacho e thunks) |
| Provedor | Não obrigatório | Deve ser um pacote de Provedor |
| Assinatura seletiva | Suporte nativo, funções seletoras | useSelector + comparação superficial |
| TypeScript | Compatível com código nativo | Bom, com inferência de tipos |
| Middleware | persist / devtools / immer, etc. | Thunk integrado + saga/epic extensível |
| Processamento assíncrono | Implementado manualmente na ação | Integrado com createAsyncThunk |
| Tamanho do pacote | ~1 KB | ~12 KB |
| Casos de uso | Aplicativos de pequeno e médio porte, compartilhamento de estado entre componentes | Aplicativos de grande porte, fluxos de dados complexos, colaboração entre equipes |
| DevTools | Compatível (requer middleware) | Suporte nativo, depuração com “viagem no tempo” |
Dicas de seleção:
- Pequenos projetos / protótipos / projetos pessoais → Zustand. Menos código, menor carga mental e o suficiente.
- Grandes projetos / Várias equipes / Padrões rigorosos exigidos → Redux Toolkit. Processos padronizados garantem estilos de codificação consistentes entre as diferentes equipes.
- Usar uma combinação dos dois também é uma abordagem comum: usar o Zustand para o estado global compartilhado entre as páginas e o Redux Toolkit para módulos de negócios complexos.
❓ Perguntas Frequentes
P: Como escolho entre o Zustand e o Context + useReducer? R: O Zustand oferece melhor desempenho — quando o Context é atualizado, todos os consumidores são renderizados novamente, enquanto o Zustand suporta assinaturas seletivas e detalhadas. O Zustand requer menos código e evita Providers aninhados. O Context + useReducer é adequado para cenários extremamente simples (como a troca de tema), mas, na maioria dos casos, o Zustand é a melhor escolha.
P: O que o Redux Toolkit simplifica em comparação com o Redux tradicional? R: O Redux tradicional exige a criação manual de constantes de tipos de ação, criadores de ação e instruções switch-case nos redutores, além da configuração separada do Redux DevTools e do middleware. O
createSlicedo Redux Toolkit gera automaticamente tipos de ação e criadores de ação, oconfigureStoreintegra automaticamente o middleware e o DevTools, e ocreateAsyncThunksimplifica os fluxos de trabalho assíncronos. Isso reduz a quantidade de código em cerca de 60%.
P: O armazenamento
Zustandpode ser usado fora dos componentes? R: Sim.useCartStore.getState()É possível recuperar o estado atual de qualquer lugar,useCartStore.setState(...)e atualizar o estado de qualquer lugar. Isso é muito útil para acessar o estado em guardas de rota, interceptadores e callbacks do WebSocket.
P: É possível usar as duas bibliotecas no mesmo projeto ao mesmo tempo? R: Com certeza. Muitos projetos começam com o Zustand e, posteriormente, incorporam o Redux Toolkit quando a complexidade de um determinado módulo aumenta. As duas bibliotecas não entram em conflito — o Zustand gerencia o estado global simples (temas, informações do usuário), enquanto o Redux Toolkit gerencia módulos de negócios complexos (processos de pedidos, fluxos de trabalho de aprovação).
P: Você só deve usar uma biblioteca de gerenciamento de estado quando
useReducernão atender às suas necessidades? R:useReduceré adequado para lógicas de estado complexas dentro de um único componente ou em um escopo localizado. Você só deve introduzir o Zustand ou o Redux Toolkit quando o estado precisar ser compartilhado entre vários componentes não relacionados, quando precisar ser persistido ou quando for necessário usar middleware. Não os utilize apenas pelo simples fato de usá-los.
P: Qual é a diferença entre a função
setdo Zustand e asetStatedo React? R: A funçãosetdo Zustand realiza uma fusão superficial por padrão (semelhante àsetStatedos componentes de classe), enquanto os redutores do Redux Toolkit, baseados no Immer, realizam atualizações profundas e imutáveis. O Zustand também pode ser usado com o middlewareimmerpara obter uma sintaxe de atualização mutável semelhante à do Redux Toolkit. Ambas as abordagens têm suas vantagens e desvantagens em termos de uso; escolha de acordo com as preferências da sua equipe.
📖 Resumo
- O Zustand é conhecido por sua API minimalista:
create(set => ({ state, actions })), ausência de provedores e subassinaturas seletivas - Middleware de persistência
persistSincronize automaticamente o estado com o localStorage usando apenas uma linha de código - O middleware do devtools integra o Zustand ao Redux DevTools, permitindo a depuração com “viagem no tempo”
- O Redux Toolkit oferece um fluxo de trabalho padronizado: createSlice → configureStore → Provider → useSelector/useDispatch
createAsyncThunkgerencia automaticamente os três estados das operações assíncronas (pendente, concluída e rejeitada)- Para projetos pequenos, recomendamos o Zustand; para projetos grandes, recomendamos o Redux Toolkit. É possível usá-los em conjunto.
- Os sistemas de middleware (as funções aninhadas do Zustand versus o padrão builder do Redux Toolkit) oferecem extensibilidade flexível
- O Zustand é adequado para 80% dos cenários de compartilhamento de estado, enquanto o Redux Toolkit é adequado para módulos de negócios complexos que exigem especificações rigorosas.
- Independentemente da opção escolhida, recomendamos que você defina sua estratégia de gerenciamento de estados logo no início do projeto, a fim de evitar os custos de migração associados a uma mudança no meio do caminho.
📝 Exercícios
- Crie um Todo Store usando o Zustand: ele deve permitir adicionar tarefas, alterar o status de conclusão, excluir tarefas, limpar as tarefas concluídas e contar o número de tarefas não concluídas. Utilize-o tanto no componente de lista quanto no componente de estatísticas para verificar a eficácia da assinatura seletiva.
- Adicione o middleware
persistao Todo Store para verificar se os dados são mantidos após a atualização da página. - Implemente um módulo de gerenciamento de usuários usando o Redux Toolkit: use
createAsyncThunkpara simular a obtenção de uma lista de usuários (com um atraso de 1 segundo antes de retornar os dados), gerencie os três estados (carregando, erro e dados) e exiba o estado de carregamento, o estado de erro e a lista de dados no componente.