//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
O Zustand oferece vários middlewares integrados: persist para armazenamento, devtools para Redux DevTools, immer para atualizações imutáveis e subscribeWithSelector para assinaturas granulares. Empilhe-os aninhando.
import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
interface AppState {
count: number;
increment: () => void;
}
export const useStore = create<AppState>()(
devtools(
persist(
immer((set) => ({
count: 0,
increment: () =>
set((state) => {
state.count += 1; // Mutação direta graças ao immer
}),
})),
{ name: "app-store" }
),
{ name: "AppStore" }
)
);// stores/settings-store.ts
import { create } from "zustand";
import { devtools, persist, subscribeWithSelector } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
interface Settings {
theme: "light" | "dark" | "system";
fontSize: number;
language: string;
notifications: {
email: boolean;
push: boolean;
sms: boolean;
};
}
interface SettingsStore extends Settings {
setTheme: (theme: Settings["theme"]) => void;
setFontSize: (size: number) => void;
setLanguage: (lang: string) => void;
toggleNotification: (channel: keyof Settings["notifications"]) => void;
resetToDefaults: () => void;
}
const defaults: Settings = {
theme: "system",
fontSize: 16,
language: "en",
notifications: { email: true, push: true, sms: false },
};
export const useSettingsStore = create<SettingsStore>()(
devtools(
subscribeWithSelector(
persist(
immer((set) => ({
...defaults,
setTheme: (theme) =>
set((state) => {
state.theme = theme;
}),
setFontSize: (size) =>
set((state) => {
state.fontSize = Math.max(12, Math.min(24, size));
}),
setLanguage: (lang) =>
set((state) => {
state.language = lang;
}),
toggleNotification: (channel) =>
set((state) => {
state.notifications[channel] = !state.notifications[channel];
}),
resetToDefaults: () =>
set((state) => {
Object.assign(state, defaults);
}),
})),
{
name: "settings-storage",
partialize: (state) => ({
theme: state.theme,
fontSize: state.fontSize,
language: state.language,
notifications: state.notifications,
}),
}
)
),
{ name: "SettingsStore" }
)
);
// Assina alterações de estado específicas
useSettingsStore.subscribe(
(state) => state.theme,
(theme) => {
document.documentElement.setAttribute("data-theme", theme);
}
);
useSettingsStore.subscribe(
(state) => state.fontSize,
(fontSize) => {
document.documentElement.style.fontSize = `${fontSize}px`;
}
);// components/settings-panel.tsx
"use client";
import { useSettingsStore } from "@/stores/settings-store";
export function SettingsPanel() {
const theme = useSettingsStore((s) => s.theme);
const fontSize = useSettingsStore((s) => s.fontSize);
const notifications = useSettingsStore((s) => s.notifications);
const setTheme = useSettingsStore((s) => s.setTheme);
const setFontSize = useSettingsStore((s) => s.setFontSize);
const toggleNotification = useSettingsStore((s) => s.toggleNotification);
const resetToDefaults = useSettingsStore((s) => s.resetToDefaults);
return (
<div>
<section>
<h3>Tema</h3>
<select value={theme} onChange={(e) => setTheme(e.target.value as any)}>
<option value="light">Claro</option>
<option value="dark">Escuro</option>
<option value="system">Sistema</option>
</select>
</section>
<section>
<h3>Tamanho da Fonte: {fontSize}px</h3>
<input
type="range"
min={12}
max={24}
value={fontSize}
onChange={(e) => setFontSize(Number(e.target.value))}
/>
</section>
<section>
<h3>Notificações</h3>
{(Object.keys(notifications) as Array<keyof typeof notifications>).map((ch) => (
<label key={ch}>
<input
type="checkbox"
checked={notifications[ch]}
onChange={() => toggleNotification(ch)}
/>
{ch}
</label>
))}
</section>
<button onClick={resetToDefaults}>Redefinir para padrões</button>
</div>
);
}set para que você possa escrever atualizações em estilo mutável que produzem estado imutável..subscribe() para aceitar um seletor e uma função de igualdade, permitindo efeitos colaterais granulares.Ordem de middleware (recomendada):
// devtools mais externo, depois subscribeWithSelector, depois persist, depois immer o mais interno
create<Store>()(
devtools(
subscribeWithSelector(
persist(
immer((set) => ({ ... })),
{ name: "store" }
)
)
)
);Middleware personalizado:
import { StateCreator, StoreMutatorIdentifier } from "zustand";
type Logger = <
T,
Mps extends [StoreMutatorIdentifier, unknown][] = [],
Mcs extends [StoreMutatorIdentifier, unknown][] = []
>(
f: StateCreator<T, Mps, Mcs>,
name?: string
) => StateCreator<T, Mps, Mcs>;
const logger: Logger = (f, name) => (set, get, store) => {
const loggedSet: typeof set = (...args) => {
set(...(args as any));
console.log(`[${name || "store"}]`, get());
};
return f(loggedSet, get, store);
};
// Uso
const useStore = create(logger((set) => ({ count: 0 }), "CountStore"));create<State>()() (invocação dupla) para ajudar o TypeScript a inferir os tipos combinados.partialize no middleware persist deve retornar um subconjunto do estado com segurança de tipo.// O padrão ()() é necessário para a inferência de tipo de middleware
export const useStore = create<State>()(
devtools(persist(immer((set) => ({ ... })), { name: "store" }))
);devtools mais externo e immer o mais interno.immer sem a importação de middleware de zustand/middleware/immer não funcionará. É um pacote separado.persist serializa o estado com JSON.stringify por padrão. Funções, Datas, Maps e Sets serão perdidos. Use partialize para excluir valores não serializáveis.devtools adiciona sobrecarga. Desabilite-o em produção: devtools(fn, { enabled: process.env.NODE_ENV === "development" }).subscribeWithSelector deve envolver persist (não o contrário) se você quiser assinar alterações de estado persistido.| Abordagem | Prós | Contras |
|---|---|---|
| Middleware Zustand integrado | Suporte oficial, bem tipado | Conjunto fixo de middlewares |
| Middleware personalizado | Adaptado às suas necessidades | Deve lidar com tipos manualmente |
| Sem middleware (store simples) | Mais simples, mais rápido | Sem persistência, devtools ou immer |
| Middleware Redux Toolkit | Ecossistema maduro | Biblioteca diferente, mais boilerplate |
persist -- salva e restaura o estado no armazenamento (localStorage por padrão).devtools -- conecta-se à extensão do navegador Redux DevTools.immer -- permite atualizações de estado em estilo mutável que produzem estado imutável.subscribeWithSelector -- permite assinar fatias de estado específicas fora do React.devtools mais externo, depois subscribeWithSelector, depois persist, e immer o mais interno.() ajuda o TypeScript a inferir corretamente os tipos combinados de middleware..subscribe() para aceitar um seletor e uma função de igualdade.useStore.subscribe(
(state) => state.theme,
(theme) => document.documentElement.setAttribute("data-theme", theme)
);set, get ou store.persist ou devtools não funcionará corretamente.JSON.stringify serializa funções como null.partialize para excluir ações e valores não serializáveis.devtools(storeCreator, {
name: "AppStore",
enabled: process.env.NODE_ENV === "development",
});subscribeWithSelector deve envolver persist (não o contrário) se você quiser assinar alterações de estado persistido.create<State>()() para deixar o TypeScript inferir os tipos combinados corretamente.persist(storeCreator, {
name: "settings-storage",
partialize: (state) => ({
theme: state.theme,
fontSize: state.fontSize,
}),
});Revisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥