Mejores prácticas de TypeScript + React
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de cada página en esta sección.
Busca en todas las páginas de la documentación
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de cada página en esta sección.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
strict: true es una familia de flags pero no incluye noUncheckedIndexedAccess o exactOptionalPropertyTypes - actívalos explícitamente para que el acceso con corchetes devuelva T | undefined y { theme: undefined } no sea silenciosamente permitido en props opcionales."jsx": "preserve" (o "react-jsx") y "moduleResolution": "bundler" para que no tengas que hacer import React en cada archivo y para que las condiciones de exports en package.json se resuelvan correctamente..d.ts usando declare global { … } solo participa en la fusión de módulos si el archivo en sí se trata como un módulo; agrega un export {} al final o la ampliación global desaparece silenciosamente.ProcessEnv en un .d.ts proporciona autocompletado en tiempo de compilación pero cero garantía en tiempo de ejecución, así que analiza process.env a través de un esquema Zod (o un auxiliar requireEnv) al inicio para que las variables faltantes fallen rápidamente.props.variant, así que desestructurar const { variant } = props rompe el enlace y quiebra el estrechamiento; cambia directamente en props.variant para mantener los campos de cada caso tipados.default: return assertNever(action) - function assertNever(x: never): never { throw new Error("Unexpected: " + x) } - así agregar una nueva variante falla la verificación de tipos en lugar de pasar silenciosamente.<T,> en genéricos de flecha: En archivos .tsx, los genéricos de función flecha chocan con la sintaxis JSX, así que escribe <T,> (coma al final) o <T extends unknown> para desambiguar el parámetro genérico de una etiqueta de elemento.React.FC no puede llevar un parámetro de tipo genérico y no puede ser async, así que escribe function List<T>(props: …) o async function Page() como declaraciones de función simple para componentes genéricos y Server Components.value! es un TypeError potencial en tiempo de ejecución, así que prefiere encadenamiento opcional, retornos tempranos, o una función de aserción personalizada que realmente lance un error con un mensaje que puedas depurar.noUncheckedIndexedAccess también se aplica a tuplas vía acceso con corchetes, así que tuple[0] se convierte en T | undefined; desestructurar const [a, b] = tuple preserva los tipos de elementos conocidos sin la unión.const routes = { home: "/", about: "/about" } satisfies Record<string, string> valida forma sin ampliar tipos literales, así que las claves mantienen sus tipos exactos "home" | "about" para búsquedas posteriores - una anotación simple : Record<string, string> descarta eso.asserts x is T estrecha todo el código posterior en la llamada, pero TypeScript te confía - si la función devuelve false en lugar de lanzar, cada tipo posterior es silenciosamente incorrecto.response.json() devuelve Promise<any> y el casting as T no proporciona protección en tiempo de ejecución; canaliza respuestas a través de un esquema Zod - const user = UserSchema.parse(await res.json()) - para que la validación y el tipo provengan de una única fuente de verdad.fetch no lanza en 4xx o 5xx - solo rechaza en fallo de red - así que siempre ramifica en response.ok y trata los que no son 2xx como errores antes de llamar a .json().createContext<T | null>(null) con un hook de guarda - function useAuth() { const ctx = useContext(AuthCtx); if (!ctx) throw new Error("Missing AuthProvider"); return ctx } - para que los bugs de proveedor faltante salgan a la superficie inmediatamente en lugar de chocar en el primer acceso a propiedad.event.target se tipea como el amplio EventTarget, mientras que event.currentTarget lleva el tipo de elemento genérico (HTMLInputElement, HTMLFormElement), así que usa currentTarget para .value, .select(), o new FormData(...).React.ComponentPropsWithoutRef<"input"> (o WithRef cuando reenvíes) para que cada atributo nativo se mantenga sincronizado con el DOM automáticamente, en lugar de redeclarar disabled, onChange, etc.JSX.Element se estrecha a un solo elemento y rechaza strings, números, arrays, y null; usa React.ReactNode para children para que los consumidores puedan pasar cualquier contenido renderizable sin luchar con el tipo.setInterval devuelve un number en navegadores y un objeto Timeout en Node - const timer = useRef<ReturnType<typeof setInterval> | null>(null) - así que este patrón mantiene el código portátil entre SSR y el cliente.params y searchParams como Promesas, así que const { id } = await context.params - desestructurar sin await te deja con un objeto Promise que silenciosamente se convierte en cadena a "[object Promise]".NextResponse.json({ error: "not found" } satisfies ApiErrorResponse) para que el valor de retorno debe coincidir con tu contrato de respuesta sin ampliar; envuelve request.json() (tipado Promise<any>) en validación Zod para que los clientes no puedan enviar cargas útiles arbitrarias.Date, FormData, arrays tipados, o Server Actions; funciones, instancias de clase, Map/Set, y Symbol no pueden cruzar, y pasarlos lanza un error de serialización en el renderizado.await directo son legales solo en Server Components - un componente Client escrito como async function compila pero lanza en tiempo de ejecución, y TypeScript no lo marca, así que aplica la división manualmente o con reglas de lint.useState<User>() sin valor inicial se amplía a User | undefined sin advertencia - usa useState<User | null>(null) para ser explícito, o proporciona un valor inicial; el setter reemplaza el state completamente, no se fusiona como class setState.Omit<T, "nonExistent"> silenciosamente devuelve el tipo completo porque TypeScript amplía el parámetro clave, así que los typos pasan desapercibidos - también recuerda que Partial y Readonly son superficiales, así que usa un DeepPartial/DeepReadonly personalizado para actualizaciones anidadas.Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥