Resumo
Arquitetura em camadas não é exclusividade do backend. Um frontend bem estruturado também tem zonas claras de responsabilidade: onde os dados são buscados e transformados, onde a lógica de UI vive, e onde os elementos visuais são renderizados.
Sem essas fronteiras, a lógica de negócio migra gradualmente para os componentes — e o código que deveria descrever a interface passa a carregar regras, validações e chamadas de API lado a lado com o JSX.
As três camadas
Camada de Serviço
Responsável por comunicação com o mundo externo: APIs, localStorage, cookies, WebSockets. Não sabe nada sobre React — são funções puras ou classes que recebem e retornam dados.
// src/features/users/userService.ts
export async function fetchUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error("Usuário não encontrado");
return res.json();
}
export async function updateUserName(
id: string,
name: string
): Promise<void> {
await fetch(`/api/users/${id}`, {
method: "PATCH",
body: JSON.stringify({ name }),
});
}Sem React, sem JSX, sem hooks. Testável com um simples fetch mockado.
Camada de Lógica (Hooks)
Responsável por orquestrar o serviço e gerenciar o estado reativo. Usa os serviços para buscar dados, expõe estado e ações para os componentes, mas não renderiza nada.
// src/features/users/useUser.ts
function useUser(id: string) {
const [user, setUser] = useState<User | null>(null);
const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");
useEffect(() => {
setStatus("loading");
fetchUser(id)
.then(data => { setUser(data); setStatus("idle"); })
.catch(() => setStatus("error"));
}, [id]);
const rename = async (name: string) => {
await updateUserName(id, name);
setUser(prev => prev ? { ...prev, name } : prev);
};
return { user, status, rename };
}O hook coordena a camada de serviço e expõe uma API limpa para o componente. Não importa de onde os dados vêm — o componente não sabe.
Camada de UI (Componentes)
Responsável por renderizar e capturar interações. Recebe dados e callbacks via props, sem saber como os dados foram buscados ou de onde vieram.
// src/features/users/UserProfile.tsx
function UserProfile({ userId }: { userId: string }) {
const { user, status, rename } = useUser(userId);
if (status === "loading") return <Skeleton />;
if (status === "error") return <ErrorMessage />;
return (
<div>
<h1>{user.name}</h1>
<button onClick={() => rename("Novo Nome")}>
Editar
</button>
</div>
);
}Por que essa separação importa
function OrderList() {
const [orders, setOrders] = useState([]);
useEffect(() => {
fetch("/api/orders")
.then(r => r.json())
.then(data =>
// lógica de negócio dentro do componente:
setOrders(data.filter(o => o.status !== "cancelled")
.map(o => ({
...o,
total: o.items.reduce((s, i) => s + i.price, 0),
}))
)
);
}, []);
return orders.map(o => (
<div key={o.id}>
{o.id} — R$ {o.total.toFixed(2)}
</div>
));
}// orderService.ts
export async function fetchActiveOrders(): Promise<Order[]> {
const res = await fetch("/api/orders");
const data = await res.json();
return data.filter(o => o.status !== "cancelled");
}
// useOrders.ts
function useOrders() {
const [orders, setOrders] = useState<Order[]>([]);
useEffect(() => {
fetchActiveOrders().then(setOrders);
}, []);
const withTotal = orders.map(o => ({
...o,
total: o.items.reduce((s, i) => s + i.price, 0),
}));
return { orders: withTotal };
}
// OrderList.tsx
function OrderList() {
const { orders } = useOrders();
return orders.map(o => (
<div key={o.id}>
{o.id} — R$ {o.total.toFixed(2)}
</div>
));
}Visualização orbital das camadas
As três camadas formam órbitas ao redor do núcleo da aplicação. Camadas internas não conhecem as externas — as dependências sempre fluem para dentro.
Dependências fluem para o centro · Passe o mouse para destacar
Sinais de que as camadas estão misturadas
fetchdiretamente dentro de componentes com JSX.- Lógica de transformação de dados (
filter,map, cálculos) dentro do return do componente. - Hooks que retornam JSX.
- Funções de serviço que importam
useStateouuseEffect.
Conceitos relacionados
- Componentes vs Containers — como a divisão UI / lógica se manifesta em componentes.
- Separação de Responsabilidades — o princípio que fundamenta toda essa organização.