Voltar ao blog
Capa do artigo: SOLID no Frontend: aplicando os princípios em componentes React
SOLIDReactTypeScriptArquitetura

SOLID no Frontend: aplicando os princípios em componentes React

Como os 5 princípios SOLID se traduzem para o mundo dos componentes React com TypeScript, com exemplos práticos do dia a dia.

15 de março de 202510 min de leitura

Arquitetura de software

SOLID não é só para o backend

Durante minha passagem pela SenseData, apliquei intensamente os princípios SOLID para separar responsabilidades entre lógica e UI em uma aplicação SaaS densa. O resultado foi direto: menos re-renders, manutenção mais fácil e features entregues mais rápido.

Vamos ver como cada princípio se traduz para o mundo React + TypeScript.

S — Single Responsibility

Cada componente deve ter uma razão para mudar. O erro mais comum é o componente "faz-tudo":

// ❌ Violando SRP
function UserCard({ userId }: { userId: string }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser).finally(() => setLoading(false));
  }, [userId]);

  if (loading) return <Spinner />;

  return (
    <div>
      <img src={user.avatar} />
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
    </div>
  );
}
// ✅ Respeitando SRP
function useUser(userId: string) {
  return useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId) });
}

function UserCard({ userId }: { userId: string }) {
  const { data: user, isLoading } = useUser(userId);
  if (isLoading) return <Spinner />;
  return <UserCardView user={user} />;
}

function UserCardView({ user }: { user: User }) {
  return (
    <div>
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
    </div>
  );
}

O — Open/Closed

Aberto para extensão, fechado para modificação. Em React, isso se traduz em composição via props:

// ✅ Componente extensível via composition
function Button({ children, icon, variant = 'primary', ...props }: ButtonProps) {
  return (
    <button className={variants[variant]} {...props}>
      {icon && <span aria-hidden="true">{icon}</span>}
      {children}
    </button>
  );
}

// Extensão sem modificar o Button original
<Button icon={<WhatsappLogo />} variant="success">Falar no WhatsApp</Button>

L — Liskov Substitution

Subtipos devem ser substituíveis pelos seus tipos base. No React, isso significa que componentes que recebem React.ComponentProps devem se comportar como o elemento nativo esperado:

// ✅ Input customizado que respeita LSP
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label: string;
  error?: string;
}

function Input({ label, error, id, ...nativeProps }: InputProps) {
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} aria-invalid={!!error} {...nativeProps} />
      {error && <span role="alert">{error}</span>}
    </div>
  );
}

I — Interface Segregation

Não force componentes a depender de props que não usam:

// ❌ Interface gordurosa
interface CardProps {
  title: string;
  description: string;
  imageUrl: string;
  author: string;
  date: string;
  onLike: () => void;
  onShare: () => void;
  onComment: () => void;
}

// ✅ Interfaces segregadas por responsabilidade
interface CardBaseProps { title: string; description: string; }
interface CardMediaProps { imageUrl: string; }
interface CardActionsProps { onLike?: () => void; onShare?: () => void; }

D — Dependency Inversion

Dependa de abstrações, não de implementações concretas. Em React, isso significa injetar dependências via props ou contexto:

// ✅ Componente não sabe como os dados chegam
interface UserListProps {
  fetchUsers: () => Promise<User[]>;
}

function UserList({ fetchUsers }: UserListProps) {
  const { data } = useQuery({ queryKey: ['users'], queryFn: fetchUsers });
  // ...
}

// Produção
<UserList fetchUsers={() => api.get('/users')} />

// Teste
<UserList fetchUsers={() => Promise.resolve(mockUsers)} />

Conclusão

SOLID no frontend não é dogma, é guia. Aplicado com bom senso, resulta em componentes menores, mais testáveis e mais fáceis de manter. O maior ganho que obtive foi na velocidade de onboarding: novos devs entendem o código muito mais rápido quando cada peça tem uma responsabilidade clara.