“`html
Dominando a Arquitetura de Redux
Abaixo eu organizo um modelo prático (e técnico) para você dominar o desenho de store, slices, side effects e selectors com consistência.
1) Estruture o Estado por Domínios (não por telas)
O erro mais comum é modelar o estado pensando em componentes: “como eu renderizo”, “qual tela está aberta”, “qual botão foi clicado”.
Redux fica mais simples quando o estado representa domínios e contratos.
Como eu divido o estado na prática
- Slice = um domínio (ex.: auth, orders, catalog).
- Estado costuma guardar: entidades normalizadas, flags de carregamento e erros.
- UI state (ex.: modal aberto) pode morar em slice quando afeta várias áreas; caso contrário, pode ficar local ao componente.
Se dois ou mais componentes precisam “concordar” sobre algo, esse algo vira estado global (Redux).
Se só um componente usa, mantenha local. Isso evita Redux virando um depósito de detalhes visuais.
2) Use Slices com responsabilidades claras
Um slice bom tem fronteiras nítidas: define o formato do estado, publica reducers para mudanças previsíveis e exporta ações
que representam eventos do domínio. O restante do código consulta o estado via selectors.
Checklist para escrever reducers que escalam
- Reducer síncrono: transforme estado em resposta a um evento já decidido (ex.: “usuário logou”, “produto foi adicionado”).
- Imutabilidade sem sofrimento: use mutação “imutável” via Redux Toolkit (immer) com intenção clara.
- Erros e status: padronize campos como status e error para o domínio.
- Evite reducers “genéricos”: ações amplas do tipo “setData” quebram o entendimento do contrato do estado.
Formatação do estado: o padrão que eu recomendo
Para cada domínio, mantenha um formato consistente. Exemplo (conceitual):
- entities: mapa por id (ou array se o volume for pequeno e estável).
- ids: quando usar normalização.
- status: “idle | loading | success | error”.
- error: string/objeto com detalhe consumível na UI.
- lastUpdated: opcional, ajuda a evitar refetch desnecessário.
3) Organize side effects: assíncrono fora do reducer
Redux deve manter decisões determinísticas nos reducers. Qualquer IO (requests, websockets, timers) entra como side effect.
A questão não é “se pode” — é como manter legibilidade, reuso e controle de estados de carregamento.
Como eu penso o fluxo
- A ação de “intenção” dispara o efeito (ex.: fetchOrdersRequested).
- O middleware/handler executa o async e despacha ações para sucesso/erro.
- O reducer só recebe o resultado e atualiza o estado do domínio.
você separa “o que fazer” (orquestração do efeito) de “o que muda” (transformação do estado),
reduzindo bugs em cenários de concorrência (ex.: duas requisições seguidas).
Concorrência e cancelamento
Em cenários reais, você vai ter múltiplos disparos (scroll infinito, filtros, navegação).
O desenho ideal inclui:
- um status por operação quando necessário (ex.: carregando lista vs carregando detalhes);
- um mecanismo para ignorar respostas “antigas” (por exemplo, usando parâmetros/versões);
- cancelamento quando fizer sentido (principalmente em navegação).
4) Selectors: performance, encapsulamento e previsibilidade
Selectors são a camada entre “como o estado é guardado” e “como a UI consome”.
Quando você cria selectors bem definidos, muda a estrutura interna do estado sem quebrar a UI.
O que um selector bom faz
- Encapsula a forma interna do estado (evita acessar state.slice.x.y em todo lugar).
- Minimiza recalculações com memoização quando necessário.
- Expõe contratos simples para componentes: lista pronta, flags e valores deriváveis.
Evite: lógica pesada em componente
Se o componente está fazendo filtro, ordenação, agregações ou transformação de entidades, você provavelmente
está perdendo performance e criando código difícil de testar.
Se um selector vira “utilitário de renderização”, ele precisa existir como selector para manter consistência e reduzir repetição.
// exemplos conceituais com Redux Toolkit (estrutura e contratos)
import { createAsyncThunk, createSlice, createSelector } from "@reduxjs/toolkit";
/** ====== Estado (domínio) ====== */
const initialState = {
ids: [],
entities: {}, // id -> item
status: "idle", // idle | loading | success | error
error: null,
lastUpdated: null
};
/** ====== Side effect ====== */
export const fetchOrders = createAsyncThunk(
"orders/fetchOrders",
async ({ customerId, signal }) => {
// IO fora do reducer
const res = await fetch(`/api/customers/${customerId}/orders`, { signal });
if (!res.ok) throw new Error("Falha ao buscar pedidos");
return res.json(); // exemplo: array de pedidos
},
{
condition: ({ customerId }, { getState }) => {
// evita refetch desnecessário (exemplo simples)
const state = getState();
// se já atualizou muito recentemente, você pode retornar false
// aqui deixei como exemplo estrutural:
return Boolean(customerId);
}
}
);
/** ====== Slice ====== */
const ordersSlice = createSlice({
name: "orders",
initialState,
reducers: {
orderAdded(state, action) {
const order = action.payload;
if (state.entities[order.id]) return; // idempotência simples
state.entities[order.id] = order;
state.ids.push(order.id);
}
},
extraReducers: (builder) => {
builder
.addCase(fetchOrders.pending, (state) => {
state.status = "loading";
state.error = null;
})
.addCase(fetchOrders.fulfilled, (state, action) => {
const orders = action.payload; // array
state.ids = [];
state.entities = {};
for (const o of orders) {
state.ids.push(o.id);
state.entities[o.id] = o;
}
state.status = "success";
state.lastUpdated = Date.now();
})
.addCase(fetchOrders.rejected, (state, action) => {
state.status = "error";
state.error = action.error?.message ?? "Erro desconhecido";
});
}
});
export const { orderAdded } = ordersSlice.actions;
export default ordersSlice.reducer;
/** ====== Selectors (encapsulamento) ====== */
const selectOrdersState = (rootState) => rootState.orders;
export const selectOrdersStatus = createSelector(
[selectOrdersState],
(orders) => orders.status
);
export const selectOrdersError = createSelector(
[selectOrdersState],
(orders) => orders.error
);
// Selector derivado: “lista pronta” para UI
export const selectOrdersList = createSelector(
[selectOrdersState],
(orders) => orders.ids.map((id) => orders.entities[id])
);
// Selector parametrizado (exemplo de detalhe por id)
export const makeSelectOrderById = () =>
createSelector(
[selectOrdersState, (_rootState, id) => id],
(orders, id) => orders.entities[id] ?? null
);
Agora que você tem um modelo arquitetural claro (domínios, slices, side effects e selectors),
continue aprofundando com outros posts em yurideveloper.com focados em padrões práticos e consistência de código.
“`
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!