“`html
Guia prático
Dominando a arquitetura de Zustand
Quando o store cresce, o problema não é “falta de Zustand”. É falta de arquitetura:
fronteiras claras, estado coeso, ações previsíveis e seletor com performance sustentada.
1) Modelando o domínio: estado coeso e responsabilidades separadas
A primeira decisão arquitetural é o que vira estado e como agrupar o que pertence junto.
Eu penso em Zustand como um runtime de UI state: coisas que mudam ao longo do fluxo do usuário e precisam ser compartilhadas.
se uma informação não muda (ou não precisa ser compartilhada), ela não entra no store.
Se muda, entra. E se muda junto, fica junto.
- Coesão: agrupe por caso de uso (ex.: carrinho, autenticação, filtros), não por “tipos” (ex.: só arrays, só flags).
-
Imutabilidade conceitual: mesmo usando mutações “internas” com immer (opcional), mantenha a intenção clara:
atualize de forma determinística e traceável. - Derivações: prefira selectors para computar valores derivados ao invés de duplicar estado.
2) Estruturando slices: quando (e como) dividir seu store
Assim que você cruza o limite do “store pequeno”, a melhor forma de manter clareza é dividir por slices.
No Zustand moderno, isso costuma ser feito compondo o store com funções e tipando as interfaces.
Crie um slice por domínio (ex.: auth, checkout, ui).
Um slice deve ter seu próprio estado + suas ações, reduzindo acoplamento.
Combine no store “raiz”.
O store final vira um “sanduíche”: ações e estado de vários slices em um único ponto de acesso.
Evite ações globais genéricas demais.
Prefira ações que refletem o domínio: signIn, applyCoupon, setFilters.
Tipagem primeiro.
Interfaces bem definidas evitam que o store vire um “saco de propriedades”.
chamar ações de um slice dentro de outro sem intenção. Se precisar, passe por funções “de orquestração”
(ex.: em um slice de use cases) para manter rastreabilidade.
3) Selectors e performance: o padrão que mantém a UI estável
A arquitetura também é como o estado flui até a renderização. O objetivo é simples:
cada componente deve re-renderizar apenas quando o que ele consome muda.
-
Selecione por “pedaços”: use selectors que retornem somente o dado necessário.
Evite selecionar o store inteiro. -
Estabilidade: evite criar objetos/arrays novos no selector sem necessidade.
Se precisar, aplique memorização (ou derive valores em camadas apropriadas). - Funções de ação: em geral, ações podem ser selecionadas diretamente para manter referência estável.
Quando a UI começa a “piscar”, geralmente é porque um componente está assinando mudanças demais.
A correção é arquitetural: ajustar seletor e, se for o caso, reorganizar slices para reduzir dependências cruzadas.
- O componente está assinando somente o estado que ele realmente renderiza?
- Os selectors retornam primitivas/estruturas estáveis?
- Você está evitando “selector gigante” que mistura domínios diferentes?
4) Fluxos previsíveis: ações, async e consistência do estado
Um store bem arquitetado tem ações com semântica clara.
Em especial, para operações assíncronas, eu model;o o ciclo completo:
iniciar → sucesso → falha → finalizar.
Estado de execução: crie campos explícitos para status, error e/ou loading.
Isso elimina “gambiarras” como inferir loading por ausência de dados.
Atualizações atômicas: minimize janelas inconsistentes.
Se a UI precisa mudar em conjunto (ex.: limpar erro + setar dados), faça em uma sequência bem definida.
Orquestração de efeitos: se um fluxo envolve múltiplos domínios, concentre a coordenação em uma camada.
Assim você evita que cada slice “saia” do seu território.
seu store vira uma fonte de verdade com transições legíveis,
e debugging fica muito mais rápido.
Exemplo: store composto por slices + ações async com estado previsível
Abaixo eu monto um desenho típico para crescer com segurança:
slices separados, ações tipadas, e status/erro padronizados para async.
import { create } from "zustand";
type AsyncStatus = "idle" | "loading" | "success" | "error";
type AuthState = {
status: AsyncStatus;
error: string | null;
userId: string | null;
};
type AuthActions = {
signIn: (email: string, password: string) => Promise<void>;
signOut: () => void;
};
type FiltersState = {
search: string;
statusFilter: "all" | "open" | "closed";
};
type FiltersActions = {
setSearch: (value: string) => void;
setStatusFilter: (value: FiltersState["statusFilter"]) => void;
};
const createAuthSlice = (set: any): AuthState & AuthActions => ({
status: "idle",
error: null,
userId: null,
signIn: async (email, password) => {
set({ status: "loading", error: null });
try {
// Exemplo: substitua por sua chamada real
const result = await fakeRequestSignIn(email, password);
set({
status: "success",
userId: result.userId,
error: null,
});
} catch (err: any) {
set({
status: "error",
error: err?.message ?? "Falha ao fazer login",
});
}
},
signOut: () => {
set({
status: "idle",
userId: null,
error: null,
});
},
});
const createFiltersSlice = (set: any): FiltersState & FiltersActions => ({
search: "",
statusFilter: "all",
setSearch: (value) => set({ search: value }),
setStatusFilter: (value) => set({ statusFilter: value }),
});
type AppStore = (AuthState & AuthActions) & (FiltersState & FiltersActions);
export const useAppStore = create<AppStore>((set, get) => ({
...createAuthSlice(set),
...createFiltersSlice(set),
}));
async function fakeRequestSignIn(email: string, password: string) {
await new Promise((r) => setTimeout(r, 600));
if (!email.includes("@") || password.length < 6) {
throw new Error("Credenciais inválidas");
}
return { userId: "u_123" };
}
// Exemplo de uso em componente:
// const userId = useAppStore(s => s.userId);
// const signIn = useAppStore(s => s.signIn);
Se um slice precisa do outro, prefira resolver isso em uma ação “de orquestração” (um use case)
ao invés de misturar responsabilidades.
“`
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!