11

Clean Architecture no Frontend React: Separando Concerns de Verdade

O problema real: regra de negócio morando dentro de componente

Abra qualquer projeto React com mais de 20 telas. Procure um useEffect que faz fetch, transforma dados, aplica regra de negócio e seta estado no mesmo bloco. Esse padrão aparece em quase todo codebase que cresceu sem separação de camadas.

O resultado é previsível: testar a lógica de cálculo de desconto exige renderizar componente, mockar hook de roteamento e simular evento de clique. Uma regra que deveria ser uma função pura de 10 linhas vira refém do React.

Clean Architecture no frontend não significa replicar a estrutura de um backend Java com 47 pastas. Significa que a regra de negócio roda sem importar React, que trocar Axios por fetch nativo não exige mexer em 30 arquivos, e que um teste unitário de domínio executa em milissegundos sem jsdom.

Se você já leu o post sobre Clean Architecture com TypeScript e Node.js, a ideia central é a mesma: dependências apontam para dentro, nunca para fora. A diferença está em como adaptar isso para um ambiente onde o framework (React) é onipresente.

As camadas e o que vai em cada uma

A divisão que funciona para projetos React de médio porte (10-50 telas, 3+ devs) usa quatro camadas:

CamadaResponsabilidadeDepende deExemplo concreto
DomainEntidades, value objects, regras purasNadacalculateOrderTotal, OrderStatus
ApplicationUse cases, orquestração de regrasDomainCreateOrderUseCase, ApplyDiscountUseCase
InfrastructureHTTP clients, storage, APIs externasApplication (via interface)HttpOrderRepository, LocalStorageCartGateway
PresentationComponentes React, hooks, páginasApplication (via interface)useCreateOrder, OrderPage

A regra de ouro: Domain não importa nada de fora. Application importa Domain. Infrastructure e Presentation implementam interfaces definidas em Application.

Domain: lógica que não sabe que React existe

// src/domain/entities/Order.ts

export type OrderStatus = "draft" | "confirmed" | "shipped" | "delivered";

export interface OrderItem {
  productId: string;
  name: string;
  unitPrice: number;
  quantity: number;
}

export interface Order {
  id: string;
  items: OrderItem[];
  status: OrderStatus;
  couponCode: string | null;
  createdAt: Date;
}

// Regra de negócio pura: desconto por cupom aplicado ao total
export function calculateOrderTotal(
  items: OrderItem[],
  discountPercent: number
): number {
  const subtotal = items.reduce(
    (sum, item) => sum + item.unitPrice * item.quantity,
    0
  );

  // Desconto nunca ultrapassa 30%, independente do cupom
  const clampedDiscount = Math.min(discountPercent, 30);

  return subtotal * (1 - clampedDiscount / 100);
}

export function canCancelOrder(order: Order): boolean {
  // Pedido só pode ser cancelado antes do envio
  return order.status === "draft" || order.status === "confirmed";
}

Esse arquivo não importa React, Axios, Zustand nem qualquer dependência externa. Testar calculateOrderTotal é chamar a função com argumentos e comparar o retorno. Sem render, sem provider, sem mock.

Application: use cases com contratos explícitos

A camada Application define o que o sistema faz sem dizer como. Ela declara interfaces (ports) que Infrastructure vai implementar.

// src/application/ports/OrderRepository.ts

import type { Order } from "@/domain/entities/Order";

// Port: contrato que a infraestrutura deve cumprir
export interface OrderRepository {
  findById(id: string): Promise<Order | null>;
  save(order: Order): Promise<void>;
  findByStatus(status: string): Promise<Order[]>;
}
// src/application/usecases/CreateOrderUseCase.ts

import type { Order, OrderItem } from "@/domain/entities/Order";
import type { OrderRepository } from "@/application/ports/OrderRepository";
import { calculateOrderTotal } from "@/domain/ent

---

Leia o artigo completo em [https://www.vivodecodigo.com.br/react/clean-architecture-frontend-react-separando-concerns](https://www.vivodecodigo.com.br/react/clean-architecture-frontend-react-separando-concerns)
Carregando publicação patrocinada...
1

Meus 2 cents! Conteúdo mt bem explicado. Eu como dev backend, sempre que preciso realizar manutenção no front me perco absurdamente porque é um arquivo pra fazer um milhão de coisas juntas.

OBS: o link para Clean Architecture com TypeScript e Node.js não é encontrado.

1

Gostei desse modo de pensar.

Eu vejo que é extremamente comum projetos front-end (não somente com react, mas quase qualquer framework frontend) sem separação de responsabilidades.

Sua sugestão, ao meu ver, parece muito interessante para termos uma maior maturidade em projetos de front.

Eu gostaria de ir além. Apesar de achar super interessante a sintaxe do jsx/tsx, eu queria ver um framework frontend que me deixasse escolher quando eu quero escrever a sintaxe do framwork (exemplo: jsx), mas ao mesmo tempo me permita ter arquivos html puros.

Na minha opinião, o react (e quem implementa jsx/tsx), esconde um pouco o html. Acho que se tivesse uma opção que fosse mais meio termo, as separações entre meu código e sintaxe do framework ficariam ainda melhores.

1
1