Uma lista das refatorações mais comuns que compensam em código real de React e TypeScript - cada uma com um rótulo em negrito, uma justificativa de uma frase e um pequeno trecho antes/depois. Use-a como uma lista de verificação pré-PR, uma referência de revisão de código ou uma auditoria de caça a picos em uma base de código existente.
Extraia valores mágicos para constantes nomeadas - Substitui literais espalhados por uma única fonte de verdade, para que ajustes não exijam um grep-and-pray (buscar e rezar) em todo o repositório.
Substitua any por unknown e estreite - Força os chamadores a provarem a forma antes do uso, capturando suposições incorretas na fronteira em vez de profundamente na UI.
// antesfunction parse(data: any) { return data.items.map(...); }// depoisfunction parse(data: unknown) { if (!isPayload(data)) throw new Error("bad payload"); return data.items.map(...);}
Prefira uniões discriminadas em vez de props opcionais - Torna estados impossíveis irrepresentáveis, para que o TypeScript rejeite loading: true com data: Foo em tempo de compilação.
Extraia efeitos repetidos para um hook personalizado - DRY (Don't Repeat Yourself - Não se Repita) a lógica transversal (polling, assinaturas, armadilhas de foco) e a torna testável isoladamente.
Substitua prop drilling por context ou um store - Remove props de passagem de componentes que não se importam com o valor, para que adicionar um novo consumidor não toque nas camadas intermediárias.
Calcule o estado derivado durante a renderização em vez de armazená-lo - Elimina uma classe inteira de bugs "esses dois estados discordam", tornando a derivação a única fonte de verdade.
Memorize computações caras com useMemo - Pula trabalho repetido em renderizações causadas por estado não relacionado, transformando um atraso visível de entrada em feedback instantâneo.
Envolva callbacks em useCallback apenas quando passados para filhos memoizados - Mantém os limites React.memo estáveis para que os descendentes pulem rerenderizações em vez de quebrar sua memoização.
Divida componentes superdimensionados por responsabilidade - Estreita o escopo da rerenderização e o escopo da revisão, para que uma alteração no cabeçalho não cause um diff no rodapé.
Substitua useState emaranhado por useReducer - Centraliza transições relacionadas em uma função para que invariantes possam ser aplicados em vez de espalhados por manipuladores.
Use as const para inferência literal - Fixa valores de string e array em seus tipos literais exatos para que eles fluam através de genéricos em vez de se alargarem para string.
Substitua enum por uma união de objetos as const - Produz melhor tree-shaking, JSON mais amigável e tipos mais claros sem as peculiaridades de runtime dos enums do TypeScript.
// antesenum Status { Open, Closed }// depoisconst Status = { Open: "open", Closed: "closed" } as const;type Status = (typeof Status)[keyof typeof Status];
Levante tipos compartilhados para um módulo dedicado - Evita a derivação de tipos quando a mesma forma é redeclarada em três componentes com campos ligeiramente diferentes.
Use satisfies para validar sem alargar - Mantém o tipo inferido estreito enquanto ainda verifica se o valor está em conformidade, para que o autocompletar permaneça preciso.
Substitua chaves de índice de array por IDs estáveis - Corrige bugs de reordenação, perda de foco de entrada e glitches de animação causados pelo React reutilizando o nó DOM errado.
Substitua fetches de useEffect por TanStack Query (ou SWR) - Obtém cache, deduplicação, retentativas e cancelamento de requisição gratuitamente, excluindo dezenas de linhas de boilerplate de carregamento/erro.
Valide dados externos com Zod na fronteira - Converte crashes de "undefined is not a function" profundamente na UI em uma única falha de parse explícita na borda.
const User = z.object({ id: z.string(), email: z.string().email() });const user = User.parse(await res.json());
Divida um mega-context em contextos direcionados - Impede que todos os consumidores rerenderizem quando uma fatia não relacionada muda, transformando um aplicativo lento em um ágil.
Extraia a lógica do formulário para react-hook-form + Zod - Remove a agitação de input controlado, centraliza a validação e torna o formulário declarativo em vez de imperativo.
const form = useForm<FormValues>({ resolver: zodResolver(Schema) });<input {...form.register("email")} />
Substitua ternários aninhados por retornos antecipados - Achata o ramificação para que o caminho feliz seja lido de cima para baixo sem manter uma árvore de parse na cabeça.
Converta operações imperativas de DOM para estado declarativo - Alinha-se com o modelo mental do React para que as rerenderizações não possam anular acidentalmente uma mutação manual do DOM.
Use encadeamento opcional e coalescência nula - Substitui pirâmides de guarda por uma única expressão que ainda lida explicitamente com os casos nulos/indefinidos.
// antesconst name = user && user.profile && user.profile.name ? user.profile.name : "guest";// depoisconst name = user?.profile?.name ?? "guest";
Mova UI estática para um Server Component - Envia menos JavaScript para o cliente e mantém a busca de dados próxima à fonte sem um roundtrip de hidratação.
// app/page.tsx (Server Component por padrão no App Router)export default async function Page() { const posts = await db.posts.findMany(); return <PostList posts={posts} />;}
Carregue rotas e widgets pesados de forma preguiçosa (lazy-load) - Mantém o bundle inicial pequeno para que a primeira pintura interativa não espere por uma biblioteca de gráficos que o usuário pode nunca abrir.
Substitua React.FC por um tipo de prop explícito - Dá a você controle sobre o contrato children e corresponde ao consenso atual da comunidade sobre tipagem de componentes.
Remova flags de loading emparelhando Suspense com uma biblioteca de dados - Remove branches isLoading em favor de um único boundary de fallback, permitindo que os componentes assumam que os dados estão presentes.
<Suspense fallback={<Skeleton />}> <UserProfile id={id} /> {/* lê dados via `use` ou uma query suspendendo */}</Suspense>
Nível de Tipos (2, 3, 12-15, 28): vitórias baratas em tempo de compilação - implemente-as primeiro; elas desbloqueiam refatorações mais seguras a jusante.
Nível de Correção de Renderização (6, 10, 16, 23, 24): remove classes de bugs, não apenas ruído - priorize em qualquer componente que tenha registrado regressões.
Nível de Desempenho (7-9, 19, 26, 27, 30): somente após uma medição real - não especule; profile primeiro.
Nível de Dados (17, 18, 30): geralmente a maior vitória em um único PR em bases de código legadas - mire em uma área de cada vez.
Nível de Estrutura (1, 4, 5, 11, 20, 21, 22, 25, 29): implementado junto com o trabalho de recurso; resista à tentação de agrupá-los em um PR de mega-refatoração.
Refatoração sem testes - Extrações de renderização pura são seguras; movimentações de lógica não são. Correção: implemente um teste de caracterização antes de tocar no comportamento.
Memoização prematura - useMemo/useCallback em todos os lugares adiciona ruído sem ganhos mensuráveis. Correção: memoize apenas após um trace do profiler ou quando um React.memo descendente depende da estabilidade referencial.
Refatorações de mega-PR - Um "PR de limpeza" com 40 arquivos é impossível de revisar e propenso a conflitos. Correção: uma decisão por PR, empilhadas se necessário.
Refatorar para recursos futuros - Moldar código para um recurso que pode nunca ser enviado é custo irrecuperável. Correção: refatore no minuto antes do recurso ser enviado, não semanas antes.
Refatorações apenas de tipos buscando 100% de rigor - Substituir todos os any de uma vez cria diffs enormes sem mudança de comportamento. Correção: habilite flags de rigor incrementalmente e corrija a derivação na fronteira do módulo.
Comece com o nível de tipos (itens 2, 3, 12-15, 28) - são apenas em tempo de compilação, de baixo risco e tornam o resto mais seguro.
Em seguida, ataque as refatorações da camada de dados (itens 17, 18, 30) para a maior redução de código em um único PR.
Deixe o desempenho (itens 7-9) para o final - toque apenas após um trace real do profiler.
Quando `useMemo` ou `useCallback` realmente valem a pena?
Quando o valor é passado para um filho React.memo que, de outra forma, rerenderizaria em cada renderização pai.
Quando a própria computação é mensuravelmente cara (ordenar/filtrar grandes listas, dados derivados pesados).
Não como um padrão - memoização especulativa adiciona ruído e pode até desacelerar as coisas através da agitação do array de dependências.
Por que preferir uniões discriminadas em vez de props opcionais?
Elas tornam estados impossíveis irrepresentáveis - loading: true com data: Foo simplesmente não será verificado pelo tipo.
Os consumidores podem fazer um switch exaustivo no discriminador e deixar o TypeScript pegar casos ausentes.
Props opcionais permitem que você esqueça acidentalmente de limpar error quando o carregamento recomeça - uniões não farão isso.
Devo sempre substituir `enum` por objetos `as const`?
Para buscas do tipo string, sim - melhor tree-shaking, JSON mais limpo, sem surpresas de mapeamento reverso de enum em runtime.
Enums numéricos podem permanecer se você estiver fazendo flags bitwise ou precisar iterar sobre valores.
A maior vantagem é que objetos as const compõem naturalmente com satisfies e tipos de literal de template.
Como sei quando dividir um contexto grande?
Se mudar uma fatia (tema) rerenderiza consumidores de fatias não relacionadas (carrinho), você superou um único contexto.
Um perfil rápido do React DevTools em uma interação direcionada mostrará a cascata de rerenderização claramente.
Divida por frequência de atualização: valores de movimento lento (auth, tema) em um contexto, valores de movimento rápido (carrinho, seleção) em outro ou em um store.
O `any` é às vezes inevitável?
Quase nunca em código de produto - unknown mais uma função de estreitamento é mais seguro em todos os casos em que any é tentador.
Internos de bibliotecas que lidam com genéricos verdadeiramente dinâmicos (Zod, TanStack Query) são a exceção honesta.
Se você precisar usar any, limite-o a uma única função e comente o porquê - não deixe que ele vaze através das fronteiras do módulo.
Quando devo refatorar um componente de classe para um componente de função?
Quando você já está tocando no arquivo - não faça isso como um PR autônomo, a menos que um hook seja especificamente necessário.
Quando os métodos de ciclo de vida são um mau ajuste para a lógica (por exemplo, limpeza de assinatura espalhada por três métodos).
Nunca como uma varredura de sexta-feira à tarde - classes funcionam bem; o valor está em desbloquear hooks ou recursos modernos.
Quão agressivo devo ser ao excluir `useEffect` para busca de dados?
Muito - a busca baseada em efeitos perde cache, deduplicação, retentativas e cancelamento que as bibliotecas fornecem gratuitamente.
Migre um caminho principal (geralmente o painel ou a visualização de lista) e observe o boilerplate de estado de carregamento desaparecer.
Reserve useEffect para efeitos colaterais verdadeiros no mundo exterior: assinaturas, temporizadores, APIs imperativas.
Por que não enviar um grande PR de refatoração?
Grandes PRs são impossíveis de revisar - os revisores os aprovam superficialmente e regressões sutis passam despercebidas.
Conflitos de rebase se acumulam com a velocidade da equipe; pequenos PRs são enviados antes que os conflitos importem.
Uma decisão por PR fornece um histórico git limpo e um caminho de reversão fácil quando algo quebra.
Qual é a cobertura mínima de testes antes de refatorar?
Pelo menos um teste de integração por caminho visível pelo usuário que você está tocando - não importa se é Playwright ou Testing Library.
Refatorações apenas de tipos são seguras sem testes porque uma falha aparecerá em tempo de compilação.
Movimentações de lógica (consolidação de reducer, extração de hook) precisam de um teste "antes" que bloqueie o comportamento atual.
Como posso parar de acumular chamadas `useState` em um único componente?
Se dois ou mais campos atualizarem juntos, agrupe-os em um useReducer com ações nomeadas.
Se o valor derivado puder ser calculado a partir do estado existente, calcule-o durante a renderização em vez de armazená-lo.
Se o estado não impulsionar a renderização, considere useRef em vez disso.
Qual é a maneira mais barata de encontrar candidatos a refatoração em uma base de código existente?
Use grep para any, as any, @ts-expect-error e // eslint-disable - eles marcam a podridão.
Execute o profiler do React DevTools nos três principais caminhos do usuário e procure por rerenderizações evitáveis.
Ordene os arquivos por número de linhas; qualquer coisa acima de ~400 linhas é quase sempre um candidato a divisão.