Zustand vs Jotai: Escolhendo o Gerenciador de Estado Correto para Sua Aplicação React

Novo no Zustand ou Jotai? Confira nossos guias detalhados primeiro:
O gerenciamento de estado no React evoluiu significativamente além da complexidade do Redux. Para projetos pequenos e médios, alternativas leves como Zustand e Jotai ganharam popularidade. Mas qual você deve escolher? Este artigo compara essas duas bibliotecas criadas pelo mesmo desenvolvedor (Daishi Kato) para ajudá-lo a tomar uma decisão informada baseada nas necessidades do seu projeto.
Principais Pontos
- Zustand usa uma abordagem centralizada, de cima para baixo, ideal para estado interconectado e colaboração em equipe
- Jotai usa uma abordagem atômica, de baixo para cima, perfeita para reatividade granular e dados que mudam rapidamente
- Ambas as bibliotecas são leves, performáticas e compatíveis com TypeScript
- Zustand é frequentemente melhor para aplicações maiores com relacionamentos de estado complexos
- Jotai se destaca em cenários que requerem pedaços independentes de estado com re-renderizações mínimas
As Origens e Filosofia do Zustand e Jotai
Ambas as bibliotecas foram criadas para resolver problemas específicos no ecossistema React, mas com abordagens diferentes:
Zustand: A Abordagem de Cima para Baixo
Zustand (alemão para “estado”) foi lançado em 2019 como uma alternativa mais simples ao Redux. Ele segue uma abordagem centralizada, de cima para baixo, para gerenciamento de estado.
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
Jotai: A Abordagem de Baixo para Cima
Jotai (japonês para “estado”) foi lançado em 2020 e se inspira no Recoil. Ele usa uma abordagem atômica, de baixo para cima, onde o estado é dividido em pequenos átomos independentes.
import { atom, useAtom } from 'jotai'
const countAtom = atom(0)
const doubleCountAtom = atom((get) => get(countAtom) * 2)
Modelos Mentais: Como Eles Abordam o Estado de Forma Diferente
Entender o modelo mental por trás de cada biblioteca é crucial para escolher a correta para seu projeto.
Modelo Baseado em Store do Zustand
O Zustand usa um único store que contém todo o seu estado e ações. Este modelo é familiar para desenvolvedores que usaram Redux:
// Criando um store
const useUserStore = create((set) => ({
user: null,
isLoading: false,
error: null,
fetchUser: async (id) => {
set({ isLoading: true });
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user, isLoading: false });
} catch (error) {
set({ error, isLoading: false });
}
}
}));
// Usando o store em um componente
function Profile({ userId }) {
const { user, fetchUser } = useUserStore(
state => ({ user: state.user, fetchUser: state.fetchUser })
);
useEffect(() => {
fetchUser(userId);
}, [userId, fetchUser]);
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
Modelo Atômico do Jotai
O Jotai divide o estado em átomos que podem ser compostos juntos. Isso é similar ao próprio useState
do React, mas com a capacidade de compartilhar estado entre componentes:
// Criando átomos
const userAtom = atom(null);
const isLoadingAtom = atom(false);
const errorAtom = atom(null);
// Criando um átomo derivado
const userStatusAtom = atom(
(get) => ({
user: get(userAtom),
isLoading: get(isLoadingAtom),
error: get(errorAtom)
})
);
// Criando um átomo de ação
const fetchUserAtom = atom(
null,
async (get, set, userId) => {
set(isLoadingAtom, true);
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
set(userAtom, user);
set(isLoadingAtom, false);
} catch (error) {
set(errorAtom, error);
set(isLoadingAtom, false);
}
}
);
// Usando átomos em um componente
function Profile({ userId }) {
const [{ user, isLoading }] = useAtom(userStatusAtom);
const [, fetchUser] = useAtom(fetchUserAtom);
useEffect(() => {
fetchUser(userId);
}, [userId, fetchUser]);
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
Considerações de Performance: Zustand vs Jotai
Ambas as bibliotecas são projetadas para serem performáticas, mas otimizam para cenários diferentes.
Perfil de Performance do Zustand
- Assinaturas Seletivas: Componentes só re-renderizam quando seu estado selecionado muda
- Tamanho do Bundle: ~2.8kB minificado e gzipped
- Suporte a Middleware: Middleware integrado para otimização de performance
- Atualizações em Lote: Agrupa automaticamente atualizações de estado
Perfil de Performance do Jotai
- Atualizações Granulares: Apenas componentes usando átomos específicos re-renderizam
- Tamanho do Bundle: ~3.5kB minificado e gzipped (pacote core)
- Otimização em Nível de Átomo: Controle granular sobre quais mudanças de estado disparam re-renderizações
- Estado Derivado: Lida eficientemente com valores computados
Para dados que mudam rapidamente e afetam apenas partes específicas da sua UI, a abordagem atômica do Jotai frequentemente resulta em menos re-renderizações. Para estado interconectado que muda com menos frequência, a abordagem do Zustand pode ser mais eficiente.
Integração com TypeScript
Ambas as bibliotecas fornecem excelente suporte ao TypeScript, mas com abordagens diferentes.
Zustand com TypeScript
interface BearState {
bears: number;
increase: (by: number) => void;
}
const useBearStore = create<BearState>((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}));
Jotai com TypeScript
interface User {
id: string;
name: string;
}
const userAtom = atom<User | null>(null);
const nameAtom = atom(
(get) => get(userAtom)?.name || '',
(get, set, newName: string) => {
const user = get(userAtom);
if (user) {
set(userAtom, { ...user, name: newName });
}
}
);
Quando Escolher Zustand
Zustand é frequentemente a melhor escolha quando:
-
Você precisa de um store centralizado: Para aplicações com estado interconectado que precisa ser acessado e modificado por muitos componentes.
-
Você está fazendo transição do Redux: A API do Zustand é mais familiar para usuários do Redux, facilitando a migração.
-
Você precisa de acesso ao estado fora do React: Zustand permite acessar e modificar estado fora dos componentes React.
-
Colaboração em equipe é uma prioridade: A abordagem de store centralizado pode ser mais fácil de manter em equipes maiores.
-
Você prefere atualizações de estado explícitas: A abordagem do Zustand torna as mudanças de estado mais rastreáveis.
Quando Escolher Jotai
Jotai se destaca quando:
-
Você precisa de reatividade granular: Para UIs com muitos pedaços independentes de estado que mudam frequentemente.
-
Você está construindo formulários complexos: A abordagem atômica do Jotai funciona bem para campos de formulário que precisam ser validados independentemente.
-
Você quer uma API similar ao useState: Se você prefere uma API que se assemelha aos hooks integrados do React.
-
Você está trabalhando com dados que mudam rapidamente: Para aplicações em tempo real onde minimizar re-renderizações é crítico.
-
Você precisa de estado derivado: Jotai facilita a criação de valores computados baseados em outros estados.
Padrões de Implementação do Mundo Real
Vamos ver alguns padrões comuns implementados em ambas as bibliotecas.
Estado de Autenticação
Com Zustand:
const useAuthStore = create((set) => ({
user: null,
isAuthenticated: false,
isLoading: false,
login: async (credentials) => {
set({ isLoading: true });
try {
const user = await loginApi(credentials);
set({ user, isAuthenticated: true, isLoading: false });
} catch (error) {
set({ isLoading: false });
throw error;
}
},
logout: async () => {
await logoutApi();
set({ user: null, isAuthenticated: false });
}
}));
Com Jotai:
const userAtom = atom(null);
const isAuthenticatedAtom = atom((get) => !!get(userAtom));
const isLoadingAtom = atom(false);
const loginAtom = atom(
null,
async (get, set, credentials) => {
set(isLoadingAtom, true);
try {
const user = await loginApi(credentials);
set(userAtom, user);
set(isLoadingAtom, false);
} catch (error) {
set(isLoadingAtom, false);
throw error;
}
}
);
const logoutAtom = atom(
null,
async (get, set) => {
await logoutApi();
set(userAtom, null);
}
);
Gerenciamento de Estado de Formulário
Com Zustand:
const useFormStore = create((set) => ({
values: { name: '', email: '', message: '' },
errors: {},
setField: (field, value) => set(state => ({
values: { ...state.values, [field]: value }
})),
validate: () => {
// Lógica de validação
const errors = {};
set({ errors });
return Object.keys(errors).length === 0;
},
submit: () => {
// Lógica de envio
}
}));
Com Jotai:
const formAtom = atom({ name: '', email: '', message: '' });
const nameAtom = atom(
(get) => get(formAtom).name,
(get, set, name) => set(formAtom, { ...get(formAtom), name })
);
const emailAtom = atom(
(get) => get(formAtom).email,
(get, set, email) => set(formAtom, { ...get(formAtom), email })
);
const messageAtom = atom(
(get) => get(formAtom).message,
(get, set, message) => set(formAtom, { ...get(formAtom), message })
);
const errorsAtom = atom({});
const validateAtom = atom(
null,
(get, set) => {
// Lógica de validação
const errors = {};
set(errorsAtom, errors);
return Object.keys(errors).length === 0;
}
);
Estratégias de Migração
Do Redux para Zustand
// Redux
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
state.value -= 1;
}
}
});
// Zustand
const useCounterStore = create((set) => ({
value: 0,
increment: () => set(state => ({ value: state.value + 1 })),
decrement: () => set(state => ({ value: state.value - 1 }))
}));
Da Context API para Jotai
// Context API
const CounterContext = createContext();
function CounterProvider({ children }) {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={{ count, setCount }}>
{children}
</CounterContext.Provider>
);
}
// Jotai
const countAtom = atom(0);
function App() {
return <>{/* Nenhum provider necessário */}</>;
}
Armadilhas Comuns e Melhores Práticas
Armadilhas do Zustand
- Fragmentação de Store: Criar muitos stores pode levar à confusão no gerenciamento de estado.
- Memoização de Seletores: Esquecer de memoizar seletores pode causar re-renderizações desnecessárias.
- Uso Excessivo de Middleware: Adicionar muitos middleware pode impactar a performance.
Armadilhas do Jotai
- Proliferação de Átomos: Criar muitos átomos sem organização pode tornar o código difícil de seguir.
- Dependências Circulares: Criar átomos que dependem uns dos outros de forma circular.
- Duplicação de Átomos: Criar acidentalmente múltiplas instâncias do mesmo átomo.
Melhores Práticas para Ambos
- Organize Estado Relacionado: Agrupe estado e ações relacionados juntos.
- Use TypeScript: Ambas as bibliotecas se beneficiam da segurança de tipos do TypeScript.
- Documente sua Estrutura de Estado: Deixe claro como seu estado está organizado.
- Teste sua Lógica de Estado: Escreva testes unitários para seu código de gerenciamento de estado.
Conclusão
Escolher entre Zustand e Jotai depende dos requisitos específicos do seu projeto. Zustand oferece uma abordagem centralizada que funciona bem para estado complexo e interconectado em aplicações maiores. Jotai fornece um modelo atômico que se destaca em reatividade granular com re-renderizações mínimas. Ambas as bibliotecas entregam soluções leves e performáticas que melhoram significativamente a complexidade do Redux mantendo compatibilidade com TypeScript.
Considere a familiaridade da sua equipe com diferentes padrões de gerenciamento de estado, as necessidades de performance da sua aplicação e sua estrutura de estado ao tomar sua decisão. Lembre-se de que você pode até usar ambas as bibliotecas na mesma aplicação, aproveitando cada uma para o que ela faz de melhor.
Perguntas Frequentes
Sim, muitos desenvolvedores usam Zustand para estado global da aplicação e Jotai para estado específico de componentes que precisa ser compartilhado pela árvore de componentes.
Sim, ambas podem escalar para aplicações grandes quando usadas adequadamente. Zustand pode ser mais fácil de manter em configurações de equipes grandes devido à sua abordagem centralizada.
Ambas são significativamente mais leves e simples que o Redux. Zustand está mais próximo do Redux em filosofia, mas com muito menos boilerplate. Jotai adota uma abordagem completamente diferente focada em estado atômico.
Zustand não requer um provider por padrão. Jotai pode ser usado sem provider para átomos globais, mas oferece um provider para estado com escopo.
Sim, ambas as bibliotecas funcionam bem com Next.js e outros frameworks React. Elas fornecem utilitários específicos para suporte a renderização do lado do servidor.