Melhores Práticas de TypeScript + React
Um resumo condensado das 25 melhores práticas mais importantes, extraídas de cada página desta seção.
Busque em todas as páginas da documentação
Um resumo condensado das 25 melhores práticas mais importantes, extraídas de cada página desta seção.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
strict: true é uma família de flags, mas não inclui noUncheckedIndexedAccess ou exactOptionalPropertyTypes - ative-as explicitamente para que o acesso por colchetes retorne T | undefined e { theme: undefined } não seja silenciosamente permitido em props opcionais."jsx": "preserve" (ou "react-jsx") e "moduleResolution": "bundler" para que você não precise importar React em cada arquivo e para que as condições exports do package.json resolvam corretamente..d.ts usando declare global { … } só participa da fusão de módulos se o próprio arquivo for tratado como um módulo; adicione um export {} no final ou as suas ampliações globais desaparecerão silenciosamente.ProcessEnv em um .d.ts fornece autocompletar em tempo de compilação, mas nenhuma garantia em tempo de execução, então analise process.env através de um esquema Zod (ou um helper requireEnv) na inicialização para que variáveis ausentes falhem rapidamente.props.variant, então desestruturar const { variant } = props quebra o link e o estreitamento; use switch diretamente em props.variant para manter os campos de cada caso tipados.default: return assertNever(action) - function assertNever(x: never): never { throw new Error("Inesperado: " + x) } - para que adicionar uma nova variante falhe na verificação de tipo em vez de prosseguir silenciosamente.<T,> em Genéricos de Seta: Em arquivos .tsx, genéricos de funções de seta colidem com a sintaxe JSX, então escreva <T,> (vírgula final) ou <T extends unknown> para desambiguar o parâmetro genérico de uma tag de elemento.React.FC não pode carregar um parâmetro de tipo genérico e não pode ser assíncrono, então escreva function List<T>(props: …) ou async function Page() como declarações de função simples para componentes genéricos e de servidor.! como um Bug: Sob verificações estritas de nulos, cada value! é um potencial TypeError em tempo de execução, então prefira encadeamento opcional, retornos antecipados ou uma função de asserção personalizada que realmente lance um erro com uma mensagem que você possa depurar.noUncheckedIndexedAccess também se aplica a tuplas via acesso por colchetes, então tuple[0] se torna T | undefined; desestruturar const [a, b] = tuple preserva os tipos de elemento conhecidos sem a união.satisfies em Vez de Anotações de Tipo: const routes = { home: "/", about: "/about" } satisfies Record<string, string> valida a forma sem alargar tipos literais, então as chaves mantêm seus tipos exatos "home" | "about" para buscas posteriores - uma anotação simples : Record<string, string> descarta isso.asserts x is T estreita todo o código subsequente no chamador, mas o TypeScript confia em você - se a função retornar false em vez de lançar um erro, todo tipo downstream estará silenciosamente incorreto.response.json() retorna Promise<any> e a conversão as T não oferece proteção em tempo de execução; canalize respostas através de um esquema Zod - const user = UserSchema.parse(await res.json()) - para que a validação e o tipo venham de uma única fonte de verdade.response.ok Explicitamente: fetch não lança erro em 4xx ou 5xx - ele só rejeita em falha de rede - então sempre ramifique em response.ok e trate não-2xx como erros antes de chamar .json().null Mais Hook de Guarda: Prefira createContext<T | null>(null) com um hook de guarda - function useAuth() { const ctx = useContext(AuthCtx); if (!ctx) throw new Error("Missing AuthProvider"); return ctx } - para que bugs de provedor ausente apareçam imediatamente em vez de travar no primeiro acesso à propriedade.event.currentTarget em Vez de event.target: event.target é tipado como o genérico EventTarget, enquanto event.currentTarget carrega o tipo genérico do elemento (HTMLInputElement, HTMLFormElement), então use currentTarget para .value, .select(), ou new FormData(...).ComponentPropsWithoutRef: Tipifique componentes wrapper com React.ComponentPropsWithoutRef<"input"> (ou WithRef ao encaminhar) para que cada atributo nativo permaneça sincronizado com o DOM automaticamente, em vez de redeclarar disabled, onChange, etc.children é ReactNode, Não JSX.Element: JSX.Element estreita para um único elemento e rejeita strings, números, arrays e null; use React.ReactNode para children para que os consumidores possam passar qualquer conteúdo renderizável sem lutar contra o tipo.ReturnType: setInterval retorna um number em navegadores e um objeto Timeout no Node - const timer = useRef<ReturnType<typeof setInterval> | null>(null) - para que este padrão mantenha o código portátil entre SSR e o cliente.params no Next.js 15: Manipuladores de rota e props de página agora recebem params e searchParams como Promises, então const { id } = await context.params - desestruturar sem await deixa você com um objeto Promise que se converte silenciosamente em string para "[object Promise]".satisfies Suas Respostas JSON: Use NextResponse.json({ error: "not found" } satisfies ApiErrorResponse) para que o valor de retorno corresponda ao seu contrato de resposta sem alargar; envolva request.json() (tipado Promise<any>) em validação Zod para que os clientes não possam enviar payloads arbitrários.Date, FormData, arrays tipados ou Ações de Servidor; funções, instâncias de classe, Map/Set e Symbol não podem cruzar, e passá-los lança um erro de serialização na renderização.async, Clientes Não: Componentes de função assíncronos e await direto são legais apenas em Componentes de Servidor - um Componente de Cliente escrito como async function compila, mas lança um erro em tempo de execução, e o TypeScript não o sinaliza, então imponha a divisão manualmente ou com regras de lint.useState: useState<User>() sem valor inicial alarga para User | undefined sem aviso - use useState<User | null>(null) para ser explícito, ou forneça um valor inicial; o setter substitui o estado inteiramente, não mescla como setState de classe.Omit Não Gera Erro em Chaves Inválidas: Omit<T, "nonExistent"> retorna silenciosamente o tipo completo porque o TypeScript alarga o parâmetro da chave, então erros de digitação passam despercebidos - lembre-se também que Partial e Readonly são rasos, então use um DeepPartial/DeepReadonly personalizado para atualizações aninhadas.Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥