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 useState e props, 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



2. Diagramas conceituais

100%
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:

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.

BASH
npm install zustand

▶ Exemplo 1: Loja “Zustand Shopping Cart”

JSX 📖 Somente leitura
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>
  )
}
82 linhas de lógica (limite de 40, somente leitura)

Principais recursos do Zustand:

▶ 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.

JSX
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}'
▶ Experimente

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.

JSX
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)
▶ Experimente

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.

BASH
npm install @reduxjs/toolkit react-redux

▶ Exemplo 4: O fluxo de trabalho completo do Redux Toolkit

JSX 📖 Somente leitura
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>
  )
}
61 linhas de lógica (limite de 40, somente leitura)

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).

JSX 📖 Somente leitura
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>
  )
}
59 linhas de lógica (limite de 40, somente leitura)

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:


❓ 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 createSlice do Redux Toolkit gera automaticamente tipos de ação e criadores de ação, o configureStore integra automaticamente o middleware e o DevTools, e o createAsyncThunk simplifica os fluxos de trabalho assíncronos. Isso reduz a quantidade de código em cerca de 60%.

P: O armazenamento Zustand pode 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 useReducer nã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 set do Zustand e a setState do React? R: A função set do Zustand realiza uma fusão superficial por padrão (semelhante à setState dos 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 middleware immer para 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


📝 Exercícios

  1. 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.
  2. Adicione o middleware persist ao Todo Store para verificar se os dados são mantidos após a atualização da página.
  3. Implemente um módulo de gerenciamento de usuários usando o Redux Toolkit: use createAsyncThunk para 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.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%