A Necessidade do Gerenciamento de Estado
A medida que as aplicacoes frontend se tornam mais complexas, o compartilhamento de estado entre componentes se torna um desafio. A escolha do padrao adequado de gerenciamento de estado impacta diretamente a manutenibilidade e escalabilidade da aplicacao.
flowchart TB
subgraph PropDrilling["Prop Drilling (Inferno de Passagem de Props)"]
App["App<br/>state: { user, theme, cart }"]
Layout["Layout<br/>← props: user, theme"]
Header["Header<br/>← props: user, theme"]
Avatar["Avatar<br/>← props: user (finalmente usado)"]
App --> Layout --> Header --> Avatar
end
Note["Problema: Componentes intermediarios apenas repassam props desnecessarias"]
Classificacao das Arquiteturas de Gerenciamento de Estado
flowchart TB
subgraph Patterns["Classificacao dos Padroes de Gerenciamento de Estado"]
subgraph Flux["1. Padrao Flux/Redux"]
F1["Store Unica + Action + Reducer<br/>Redux, Zustand"]
end
subgraph Atomic["2. Padrao Atomic"]
A1["Unidades de estado pequenas e distribuidas (Atom)<br/>Jotai, Recoil"]
end
subgraph Proxy["3. Padrao Proxy-based"]
P1["Rastreamento automatico via Proxy<br/>Valtio, MobX"]
end
subgraph Signal["4. Padrao Signal"]
S1["Reatividade de granularidade fina<br/>Solid.js Signals, Preact Signals"]
end
end
Padrao Flux/Redux
Conceitos Basicos
flowchart LR
View["View"] -->|Action| Dispatcher["Dispatcher"]
Dispatcher -->|dispatch| Store["Store<br/>(Reducer)"]
Store -->|notify| State["State<br/>(somente leitura)"]
State --> View
Principios:
- Fluxo de dados unidirecional
- Estado e somente leitura (Imutavel)
- Mudancas apenas atraves de Actions
- Reducers sao funcoes puras
Implementacao Redux
// store/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface User {
id: string;
name: string;
email: string;
}
interface UserState {
currentUser: User | null;
isLoading: boolean;
error: string | null;
}
const initialState: UserState = {
currentUser: null,
isLoading: false,
error: null,
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
loginStart: (state) => {
state.isLoading = true;
state.error = null;
},
loginSuccess: (state, action: PayloadAction<User>) => {
state.currentUser = action.payload;
state.isLoading = false;
},
loginFailure: (state, action: PayloadAction<string>) => {
state.error = action.payload;
state.isLoading = false;
},
logout: (state) => {
state.currentUser = null;
},
},
});
export const { loginStart, loginSuccess, loginFailure, logout } = userSlice.actions;
export default userSlice.reducer;
// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
import cartReducer from './cartSlice';
export const store = configureStore({
reducer: {
user: userReducer,
cart: cartReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// hooks/useAppDispatch.ts
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from '../store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// components/UserProfile.tsx
function UserProfile() {
const { currentUser, isLoading, error } = useAppSelector((state) => state.user);
const dispatch = useAppDispatch();
const handleLogout = () => {
dispatch(logout());
};
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!currentUser) return <div>Please login</div>;
return (
<div>
<h1>{currentUser.name}</h1>
<button onClick={handleLogout}>Logout</button>
</div>
);
}
Implementacao Zustand
// store/useStore.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
interface User {
id: string;
name: string;
email: string;
}
interface UserStore {
// State
currentUser: User | null;
isLoading: boolean;
error: string | null;
// Actions
login: (email: string, password: string) => Promise<void>;
logout: () => void;
updateProfile: (updates: Partial<User>) => void;
}
export const useUserStore = create<UserStore>()(
devtools(
persist(
immer((set, get) => ({
currentUser: null,
isLoading: false,
error: null,
login: async (email, password) => {
set((state) => {
state.isLoading = true;
state.error = null;
});
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
const user = await response.json();
set((state) => {
state.currentUser = user;
state.isLoading = false;
});
} catch (error) {
set((state) => {
state.error = error.message;
state.isLoading = false;
});
}
},
logout: () => {
set((state) => {
state.currentUser = null;
});
},
updateProfile: (updates) => {
set((state) => {
if (state.currentUser) {
Object.assign(state.currentUser, updates);
}
});
},
})),
{ name: 'user-storage' }
),
{ name: 'UserStore' }
)
);
// Selector (Otimizacao de Performance)
export const useCurrentUser = () => useUserStore((state) => state.currentUser);
export const useIsLoading = () => useUserStore((state) => state.isLoading);
// components/UserProfile.tsx
function UserProfile() {
const currentUser = useCurrentUser();
const logout = useUserStore((state) => state.logout);
if (!currentUser) return null;
return (
<div>
<h1>{currentUser.name}</h1>
<button onClick={logout}>Logout</button>
</div>
);
}
Padrao Atomic
Implementacao Jotai
// atoms/userAtoms.ts
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
interface User {
id: string;
name: string;
email: string;
}
// Atom basico
export const currentUserAtom = atom<User | null>(null);
// Atom persistido
export const themeAtom = atomWithStorage<'light' | 'dark'>('theme', 'light');
// Atom derivado (somente leitura)
export const isLoggedInAtom = atom((get) => get(currentUserAtom) !== null);
// Atom derivado (leitura e escrita)
export const userNameAtom = atom(
(get) => get(currentUserAtom)?.name ?? 'Guest',
(get, set, newName: string) => {
const user = get(currentUserAtom);
if (user) {
set(currentUserAtom, { ...user, name: newName });
}
}
);
// Atom assincrono
export const userProfileAtom = atom(async (get) => {
const user = get(currentUserAtom);
if (!user) return null;
const response = await fetch(`/api/users/${user.id}/profile`);
return response.json();
});
// Atom de acao
export const loginAtom = atom(
null,
async (get, set, { email, password }: { email: string; password: string }) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
const user = await response.json();
set(currentUserAtom, user);
}
);
export const logoutAtom = atom(null, (get, set) => {
set(currentUserAtom, null);
});
// components/UserProfile.tsx
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
function UserProfile() {
const currentUser = useAtomValue(currentUserAtom);
const [userName, setUserName] = useAtom(userNameAtom);
const logout = useSetAtom(logoutAtom);
if (!currentUser) return null;
return (
<div>
<input
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
<button onClick={logout}>Logout</button>
</div>
);
}
Implementacao Recoil
// atoms/userState.ts
import { atom, selector, selectorFamily } from 'recoil';
interface User {
id: string;
name: string;
email: string;
}
// Atom
export const currentUserState = atom<User | null>({
key: 'currentUser',
default: null,
});
// Selector (estado derivado)
export const isLoggedInState = selector({
key: 'isLoggedIn',
get: ({ get }) => get(currentUserState) !== null,
});
// Selector assincrono
export const userProfileState = selector({
key: 'userProfile',
get: async ({ get }) => {
const user = get(currentUserState);
if (!user) return null;
const response = await fetch(`/api/users/${user.id}/profile`);
return response.json();
},
});
// Selector com parametros (SelectorFamily)
export const userPostsState = selectorFamily({
key: 'userPosts',
get: (userId: string) => async () => {
const response = await fetch(`/api/users/${userId}/posts`);
return response.json();
},
});
// components/UserProfile.tsx
import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil';
function UserProfile() {
const [currentUser, setCurrentUser] = useRecoilState(currentUserState);
const isLoggedIn = useRecoilValue(isLoggedInState);
const profile = useRecoilValue(userProfileState);
const logout = () => setCurrentUser(null);
if (!isLoggedIn) return <div>Please login</div>;
return (
<div>
<h1>{currentUser?.name}</h1>
<p>{profile?.bio}</p>
<button onClick={logout}>Logout</button>
</div>
);
}
flowchart TB
subgraph FluxPattern["Flux (Redux/Zustand)"]
Store["Single Store<br/>user | cart | ui | ..."]
Store --> CA["Component A"]
Store --> CB["Component B"]
Store --> CC["Component C"]
end
subgraph AtomicPattern["Atomic (Jotai/Recoil)"]
A1["Atom"] --> CompA["Component A"]
A2["Atom"] --> Derived["Derived Atom"]
A3["Atom"] --> Derived
Derived --> CompB["Component B"]
A4["Atom"] --> CompC["Component C"]
end
Caracteristicas:
- Flux: Centralizado, previsivel, facil de depurar
- Atomic: Distribuido, granularidade fina, adequado para code splitting
Padrao Proxy-based
Implementacao Valtio
// store/userStore.ts
import { proxy, useSnapshot } from 'valtio';
import { devtools } from 'valtio/utils';
interface User {
id: string;
name: string;
email: string;
}
interface UserStore {
currentUser: User | null;
isLoading: boolean;
error: string | null;
}
export const userStore = proxy<UserStore>({
currentUser: null,
isLoading: false,
error: null,
});
// Habilitar DevTools
devtools(userStore, { name: 'userStore' });
// Acoes (modificacao direta possivel)
export const userActions = {
async login(email: string, password: string) {
userStore.isLoading = true;
userStore.error = null;
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
userStore.currentUser = await response.json();
} catch (error) {
userStore.error = error.message;
} finally {
userStore.isLoading = false;
}
},
logout() {
userStore.currentUser = null;
},
updateName(name: string) {
if (userStore.currentUser) {
userStore.currentUser.name = name; // Modificacao direta possivel
}
},
};
// components/UserProfile.tsx
function UserProfile() {
// Obter snapshot somente leitura com useSnapshot
const snap = useSnapshot(userStore);
if (snap.isLoading) return <div>Loading...</div>;
if (!snap.currentUser) return null;
return (
<div>
<input
value={snap.currentUser.name}
onChange={(e) => userActions.updateName(e.target.value)}
/>
<button onClick={userActions.logout}>Logout</button>
</div>
);
}
Comparacao de Bibliotecas de Gerenciamento de Estado
| Caracteristica | Redux Toolkit | Zustand | Jotai | Recoil | Valtio |
|---|---|---|---|---|---|
| Tamanho do Bundle | Grande | Pequeno | Pequeno | Medio | Pequeno |
| Curva de Aprendizado | Alta | Baixa | Baixa | Media | Baixa |
| Boilerplate | Muito | Pouco | Pouco | Medio | Pouco |
| DevTools | Completo | Disponivel | Disponivel | Disponivel | Disponivel |
| TypeScript | Bom | Excelente | Excelente | Bom | Excelente |
| Suporte SSR | Bom | Bom | Excelente | Complexo | Bom |
| Uso fora do React | Possivel | Possivel | Nao | Nao | Possivel |
Criterios de Escolha
Guia de Selecao de Biblioteca de Gerenciamento de Estado
Escala do Projeto/Equipe:
| Escala | Recomendacao | Motivo |
|---|---|---|
| Grande/Muitas pessoas | Redux Toolkit | Convencoes claras, ecossistema rico |
| Media/Poucas pessoas | Zustand | Simples, baixa curva de aprendizado |
| Pequena/Prototipo | Jotai/Valtio | Configuracao minima |
Requisitos de Arquitetura:
| Requisito | Recomendacao |
|---|---|
| Previsibilidade | Redux/Zustand |
| Atualizacoes granulares | Jotai/Recoil |
| Operacoes mutaveis | Valtio |
| Server Components | Jotai (ideal) |
Melhores Praticas
// 1. Normalizacao do estado
interface NormalizedState {
users: {
byId: Record<string, User>;
allIds: string[];
};
posts: {
byId: Record<string, Post>;
allIds: string[];
};
}
// 2. Uso de estado derivado
const selectUserPosts = (state: RootState, userId: string) =>
state.posts.allIds
.map(id => state.posts.byId[id])
.filter(post => post.authorId === userId);
// 3. Separacao de processamento assincrono
// Estado do servidor com TanStack Query / SWR
// Estado do cliente com Zustand / Jotai
// 4. Divisao em granularidade adequada
// ERRADO: Store unica gigante
// CERTO: Stores divididas por funcionalidade