Vinte solicitações de refatoração do mundo real como elas chegam - algumas como comentários de revisão de PR, outras como reclamações de stakeholders que acabam sendo problemas de forma de código, outras como tarefas de limpeza de um tech lead. Cada entrada mostra a solicitação como escrita, o diagnóstico do que está realmente errado, uma refatoração concisa e por que a nova forma é melhor.
Trate isso como uma lista de verificação: examine os 20 títulos primeiro e marque quais descrevem o código que você mantém no momento.
A linha "Por que isso funciona" é a parte a memorizar - ela diz quando a mesma refatoração se aplica a um arquivo diferente.
A maioria das refatorações aqui são do tamanho de um PR. Se um único item se transformar em um projeto de vários dias, o diagnóstico provavelmente está errado.
Envie uma refatoração por PR. Agrupar uma conversão de Server Component com uma passagem de tipagem torna a revisão hostil.
"O arquivo <Dashboard /> continua crescendo a cada sprint. Os revisores gastam 40 minutos em cada PR e ainda perdem coisas. Podemos limpá-lo?"
Diagnóstico: Um componente renderiza o layout, busca três recursos, os formata, rastreia análises e possui quatro pedaços de estado de UI. Viola a responsabilidade única em todos os eixos.
Por que isso funciona: Cada filho tem um motivo para mudar. Os revisores comparam um painel de cada vez, e o Server Component pode buscar em paralelo sem um useEffect.
"A prop currentUser é passada por Layout, Sidebar, Nav, NavItem e, finalmente, LogoutButton. Adicionar um campo ao objeto user significa editar cinco arquivos."
Diagnóstico: Prop drilling. Os componentes intermediários não usam user - eles apenas o encaminham.
"Os usuários reclamam que a página do produto mostra um esqueleto de carregamento por meio segundo, mesmo quando já a visitaram. Podemos torná-la instantânea?"
Diagnóstico: Um Client Component busca em useEffect. O servidor não retorna dados, o cliente monta e, em seguida, busca. O flash é inevitável nesta forma.
Por que isso funciona: Mover a busca para um Server Component incorpora os dados na primeira resposta HTML. Sem spinner, sem cascata do cliente, sem useEffect.
"Lighthouse diz que enviamos 600 KB de JS para uma página que é em sua maioria conteúdo estático. O tech lead sinalizou que tudo é um Client Component."
Diagnóstico:"use client" está no topo do wrapper da rota. Cada título estático, barra lateral e rodapé viaja para o navegador como JS.
Refatoração:
// app/dashboard/page.tsx - sem diretivaexport default function Page() { return ( <Layout> <Header /><Sidebar /> <InteractiveChart />{/* este é "use client" */} </Layout> );}
Por que isso funciona: A fronteira vive na menor folha interativa. O markup estático permanece no servidor e é enviado como HTML, não JS.
Por que isso funciona:as const torna os valores tipos literais. Renomear a constante falha na compilação em todos os consumidores em vez de se comportar silenciosamente mal.
"Erros no Sentry mencionam Cannot read properties of undefined. O resultado do fetch é tipado como any e acessamos data.user.profile.name."
Diagnóstico:any encurta todos os checks. A forma da resposta da API é desconhecida para o compilador.
Refatoração:
import { z } from "zod";const User = z.object({ profile: z.object({ name: z.string() }) });const res = await fetch("/api/user");const user = User.parse(await res.json());
Por que isso funciona: Analisar na fronteira transforma "any" em um tipo conhecido. Respostas ruins lançam na borda em vez de falhar quatro componentes mais adiante.
"Pesquisa, a barra lateral, o badge da caixa de entrada e o cabeçalho chamam /api/notifications. Eles divergem constantemente e um sempre tem um cache desatualizado."
Diagnóstico: Lógica de carregamento de dados duplicada. Cada componente tem seu próprio useEffect, seu próprio estado de carregamento e seu próprio comportamento de retentativa.
Por que isso funciona: Uma chave de cache, um fetcher, uma única fonte de verdade. As invalidações se propagam para todos os assinantes automaticamente.
"O formulário de inscrição continua crescendo à medida que adicionamos campos. As regras de validação vivem em uma escada if gigante e enviamos bugs de validação a cada lançamento."
Diagnóstico: Gerenciamento de estado imperativo para um formulário que deveria ser declarativo.
Refatoração:
"use client";import { useForm } from "react-hook-form";import { zodResolver } from "@hookform/resolvers/zod";import { z } from "zod";const Schema = z.object({ email: z.string().email(), password: z.string().min(12),});const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(Schema),});
Por que isso funciona: O schema é a especificação. Validação, exibição de erros e tipos vêm da mesma fonte - adicionar um campo é uma mudança de uma linha.
Por que isso funciona:useEffect mais estado capturam a mesma intenção - "fazer algo quando a contagem muda" - sem armadilhas de ciclo de vida ou vinculação de this.
Por que isso funciona: Identidade estável. O React rastreia cada linha por seu ID real, então remover um item não desloca o estado interno para o próximo.
"Este card tem loading ? skeleton : error ? errorView : empty ? emptyState : data ? <Card /> : null e ninguém consegue ler."
Diagnóstico: Renderização condicional comprimida em uma única expressão.
Refatoração:
function CardView({ state }: { state: ViewState }) { if (state.kind === "loading") return <Skeleton />; if (state.kind === "error") return <ErrorView error={state.error} />; if (state.kind === "empty") return <EmptyState />; return <Card data={state.data} />;}
Por que isso funciona: Uma união discriminada + retornos antecipados leem de cima para baixo. Cada ramo é editável independentemente e a união força você a lidar com cada caso.
Por que isso funciona: Refs permitem que a biblioteca se inscreva no mesmo ciclo de vida que o React já gerencia. Sem querySelector, sem nós desmontados, sem falhas em testes.
Por que isso funciona: A ref compartilha uma identidade entre renderizações, então o intervalo lê o valor mais recente sem se inscrever novamente a cada mudança de estado.
Por que isso funciona: Valores derivados computados durante a renderização estão sempre atualizados. Não há nada para manter em sincronia porque há apenas uma fonte.
Por que isso funciona:useCallback preserva a identidade da função entre renderizações. A comparação rasa de memo agora vê a mesma prop e pula a subárvore.
Por que isso funciona: Classes Tailwind são resolvidas em tempo de compilação e dedupicadas em todo o bundle. Tokens de espaçamento se tornam aplicáveis em vez de artesanais.