Solucionado (ver solução)
Solucionado
(ver solução)
4
respostas

[Dúvida] Uma dúvida honesta sobre regra de negócio

Olá pessoal, estou transitando da área de Redes (com muito foco em ISP) para a área de Front-End e me bateu uma dúvida honestíssima.
Quando ocorre essa separação da regra de negócio e da montagem dos componentes (o que vai dentro do que)?

Tentando trazer em termos mais claros baseado neste exemplo: "Quando o arquivo App.jsx vai ter lógica aplicada e quando ele não terá?" Existe algum manual de boas práticas ou é uma regra não escrita da área?

Sei que na área de tecnologia muitas vezes iremos responder com "depende", mas gostaria de tentar entender através de alguns exemplos de como seria esse "depende". Quais são os critérios que são usados para separar a lógica da montagem do elemento?

4 respostas

Opa! Excelente pergunta, Matheus!

A verdade é que não existe um "manual oficial" pra isso, mas existem alguns padrões que a comunidade foi desenvolvendo ao longo do tempo e que funcionam muito bem na prática. O mais famoso deles é a separação entre Smart Components e Dumb Components (ou, se preferir, "componentes inteligentes" e "componentes burros" — eu sei, os nomes não são lá muito simpáticos).

Pensa comigo: quando você monta uma tela, existem duas preocupações bem diferentes acontecendo ao mesmo tempo. Uma é a lógica — de onde vêm os dados? Como eu faço login? O que acontece quando o usuário clica no botão? A outra é a apresentação — como esse botão deve parecer? Qual a estrutura do HTML? Como organizo os elementos na tela?

A ideia dos smart e dumb components é justamente separar essas duas preocupações. Dumb Components (ou componentes de apresentação) são aqueles que só sabem se desenhar. Eles recebem dados via props e renderizam. Não fazem fetch, não guardam estado complexo, não sabem nada sobre "de onde os dados vieram". Já os Smart Components (ou componentes container) são os que carregam a "inteligência". Eles sabem buscar dados, gerenciar estado, lidar com efeitos colaterais. E aí, quando têm tudo pronto, passam essas informações pros dumb components renderizarem.

Voltando pro seu exemplo do App.jsx — quando ele vai ter lógica e quando não vai? Imagina um app bem simples, tipo uma lista de tarefas. Se o App.jsx é o único componente e o app inteiro cabe ali, faz sentido ele ter a lógica. Algo assim:

function App() {
  const [tarefas, setTarefas] = useState([]);
  
  function adicionarTarefa(texto) {
    setTarefas([...tarefas, { id: Date.now(), texto }]);
  }
  
  return (
    <div>
      <h1>Minhas Tarefas</h1>
      <FormularioTarefa onAdicionar={adicionarTarefa} />
      <ul>
        {tarefas.map(t => <li key={t.id}>{t.texto}</li>)}
      </ul>
    </div>
  );
}

Nesse caso, o App é um smart component. Ele gerencia o estado e passa as funções e dados pros filhos. Agora, conforme o app cresce, você pode querer que o App.jsx seja só um "organizador" — ele monta a estrutura geral da aplicação, mas delega a lógica pra outros lugares:

function App() {
  return (
    <AuthProvider>
      <Layout>
        <Header />
        <main>
          <Outlet />
        </main>
        <Footer />
      </Layout>
    </AuthProvider>
  );
}

Aqui o App virou mais um dumb component (ou pelo menos, um componente de composição). Ele não sabe nada sobre tarefas, usuários ou dados. Ele só organiza a estrutura e deixa os providers e as rotas cuidarem do resto.

Depois de alguns anos trabalhando com React, eu desenvolvi uma espécie de "checklist" pra decidir onde colocar a lógica. Funciona mais ou menos assim: esse componente precisa ser reutilizado em outros lugares? Se sim, ele provavelmente deveria ser dumb. Um botão, um card, um input — esses caras não deveriam saber de onde os dados vêm. Esse componente representa uma "página" ou uma "funcionalidade completa"? Aí sim, ele pode (e provavelmente vai) ter lógica. A lógica está ficando complexa demais? Se você olha pro componente e ele tem 15 useStates, 3 useEffects e você já não lembra mais o que cada coisa faz, é hora de extrair essa lógica pra um lugar separado.

E é aí que os hooks customizados entram. Uma coisa que mudou muito a forma como eu organizo código foi entender o papel dos hooks na arquitetura — eu até escrevi um artigo sobre isso se você quiser se aprofundar. A ideia é simples: ao invés de deixar toda a lógica dentro do componente, você extrai ela pra um hook. O componente fica responsável só pela renderização. Olha a diferença:

Antes (tudo junto):

function ListaProdutos() {
  const [produtos, setProdutos] = useState([]);
  const [loading, setLoading] = useState(true);
  const [erro, setErro] = useState(null);
  
  useEffect(() => {
    fetch('/api/produtos')
      .then(res => res.json())
      .then(data => {
        setProdutos(data);
        setLoading(false);
      })
      .catch(err => {
        setErro(err);
        setLoading(false);
      });
  }, []);
  
  if (loading) return <p>Carregando...</p>;
  if (erro) return <p>Deu ruim: {erro.message}</p>;
  
  return (
    <ul>
      {produtos.map(p => <ProdutoCard key={p.id} produto={p} />)}
    </ul>
  );
}

Depois (com hook customizado):

function useProdutos() {
  const [produtos, setProdutos] = useState([]);
  const [loading, setLoading] = useState(true);
  const [erro, setErro] = useState(null);
  
  useEffect(() => {
    fetch('/api/produtos')
      .then(res => res.json())
      .then(data => {
        setProdutos(data);
        setLoading(false);
      })
      .catch(err => {
        setErro(err);
        setLoading(false);
      });
  }, []);
  
  return { produtos, loading, erro };
}
function ListaProdutos() {
  const { produtos, loading, erro } = useProdutos();
  
  if (loading) return <p>Carregando...</p>;
  if (erro) return <p>Deu ruim: {erro.message}</p>;
  
  return (
    <ul>
      {produtos.map(p => <ProdutoCard key={p.id} produto={p} />)}
    </ul>
  );
}

Percebe como o componente ListaProdutos ficou mais limpo? Ele não sabe mais como os produtos são buscados. Ele só sabe que precisa de produtos, loading e erro — e o hook cuida do resto.

Sobre o "depende" que você mencionou — você tem razão, a resposta muitas vezes é essa. Mas deixa eu tentar destrinchar: depende do tamanho do projeto (em um projeto pequeno, não faz sentido criar uma arquitetura super elaborada); depende de quantas pessoas trabalham no código (se é só você, você consegue manter tudo na cabeça — se tem uma equipe, separar ajuda todo mundo); depende de quanto o projeto vai crescer (se você sabe que aquele MVP vai virar um produto grande, vale começar organizado); e depende da complexidade da lógica (se envolve só mostrar uma lista, talvez não precise de tanta separação — se envolve autenticação, cache, retry automático, aí sim vale extrair).

Uma forma de pensar que me ajuda: quando eu olho pra um componente e fico em dúvida, eu me pergunto "se eu precisasse testar esse componente, o que eu testaria?". Se a resposta é "eu testaria se ele renderiza corretamente dado certas props", então ele provavelmente deveria ser um dumb component. Se a resposta é "eu testaria se ele busca os dados certos e trata os erros direito", então a lógica deveria estar em outro lugar (um hook, um service) que pode ser testada separadamente.

Espero que tenha clareado! E não se preocupa em acertar de primeira — eu mesmo já refatorei código dezenas de vezes porque comecei com tudo junto e depois fui separando conforme o projeto crescia. Faz parte. O importante é ir desenvolvendo esse senso de "isso aqui tá ficando grande demais, preciso separar", e isso vem com a prática.

Fala, Vinny - O careca barbudo mais carismático da alura (rs). Obrigado pela sua resposta e pelo tempo dedicado para elaborar a mesma. Ficou claro demais em alguns pontos aqui e eu acho que, à medida que eu for aumentando o contato, vai ficar mais clara a decisão de quando coloco ou não a regra de negócios no componente.

"Sobre o "depende" que você mencionou — você tem razão, a resposta muitas vezes é essa. Mas deixa eu tentar destrinchar: depende do tamanho do projeto (em um projeto pequeno, não faz sentido criar uma arquitetura super elaborada); depende de quantas pessoas trabalham no código (se é só você, você consegue manter tudo na cabeça — se tem uma equipe, separar ajuda todo mundo); depende de quanto o projeto vai crescer (se você sabe que aquele MVP vai virar um produto grande, vale começar organizado); e depende da complexidade da lógica (se envolve só mostrar uma lista, talvez não precise de tanta separação — se envolve autenticação, cache, retry automático, aí sim vale extrair)."

No parágrafo acima você explica exatamente como eu entendo fazendo os paralelos com o cenário de onde eu vinha. Já que expansão era uma preocupação recorrente ( e isso eu falava antes de sair das soluções de distribuição e ir para núcleo).

Quanto ao parágrafo seguinte, no trecho que você fala dos testes, aí é o momento que realmente termina de clarear. Então, eventualmente trabalharemos com um App.jsx que será só um organizador de componentes e cada componente terá a sua própria lógica (e regra de negócio), pois o código fica mais limpo/organizado/legível.

Creio que agora vou conseguir dar sequência com a cabeça mais tranquila.

solução!

Tamo junto, Matheus!

Qualquer coisa, tamo aqui :)