La productividad con TypeScript no viene solo de conocer la sintaxis, sino de dominar las técnicas que reducen el ruido de tipos, mejoran la legibilidad del código en el editor y detectan errores antes. Estos 20 trucos son los más útiles del día a día, ordenados por impacto.
1. satisfies: validar sin ampliar el tipo
// Sin satisfies: el tipo se amplía a string | number
const paleta = {
rojo: [255, 0, 0],
verde: '#00ff00',
};
paleta.rojo.toUpperCase(); // Error en runtime: es un array
// Con satisfies: valida contra el tipo pero conserva el tipo inferido
type ColorPaleta = Record<string, [number, number, number] | string>;
const paleta2 = {
rojo: [255, 0, 0],
verde: '#00ff00',
} satisfies ColorPaleta;
paleta2.rojo.map(x => x / 255); // OK: TypeScript sabe que es [number, number, number]
paleta2.verde.toUpperCase(); // OK: TypeScript sabe que es string
2. NoUncheckedIndexedAccess
// tsconfig.json: "noUncheckedIndexedAccess": true
const items = ['a', 'b', 'c'];
const primero = items[0];
// Sin la opción: string
// Con la opción: string | undefined fuerza a comprobar antes de usar
if (primero !== undefined) {
console.log(primero.toUpperCase()); // OK
}
3. Extract para filtrar unions
type Evento =
| { tipo: 'click'; x: number; y: number }
| { tipo: 'tecla'; codigo: string }
| { tipo: 'scroll'; delta: number };
type EventoClick = Extract<Evento, { tipo: 'click' }>;
// { tipo: 'click'; x: number; y: number }
type EventosConCodigo = Extract<Evento, { codigo: string }>;
// { tipo: 'tecla'; codigo: string }
4. Exclude para eliminar miembros de un union
type Primitivo = string | number | boolean | null | undefined; type PrimitivoNoNulo = Exclude<Primitivo, null | undefined>; // string | number | boolean
5. Simplify para hover legible en el editor
type Simplify<T> = { [K in keyof T]: T[K] } & {};
// Sin Simplify: el hover muestra Omit<Usuario, 'id'> & { temporal: true }
// Con Simplify: muestra { nombre: string; email: string; temporal: true }
type UsuarioTemporal = Simplify<Omit<Usuario, 'id'> & { temporal: true }>;
6. type-fest: utility types adicionales
import type { Promisable, JsonValue, SetOptional, SetRequired } from 'type-fest';
// Promisable<T>: T | Promise<T>
function procesar(dato: Promisable<string>): Promise<string> {
return Promise.resolve(dato);
}
// SetOptional: hacer opcionales solo algunos campos
type UsuarioEdicion = SetOptional<Usuario, 'email' | 'edad'>;
// JsonValue: cualquier valor serializable a JSON
function serializar(v: JsonValue): string { return JSON.stringify(v); }
7. NoInfer para controlar la inferencia
// TypeScript 5.4+
// Problema: TypeScript infiere T del valor por defecto, no del array
function elegirPrimero<T>(opciones: T[], defecto: NoInfer<T>): T {
return opciones[0] ?? defecto;
}
elegirPrimero(['a', 'b'], 'c'); // OK
// elegirPrimero(['a', 'b'], 123); // Error: 123 no es string
8. Const assertion para tipos literales
const RUTAS = {
inicio: '/',
usuarios: '/usuarios',
config: '/config',
} as const;
type Ruta = typeof RUTAS[keyof typeof RUTAS]; // '/' | '/usuarios' | '/config'
function navegar(ruta: Ruta): void { location.href = ruta; }
// navegar('/otra'); // Error: '/otra' no es una Ruta válida
9. Branded types para mayor seguridad
type UserId = number & { readonly _marca: 'UserId' };
type ProductoId = number & { readonly _marca: 'ProductoId' };
function crearUserId(id: number): UserId { return id as UserId; }
function obtenerUsuario(id: UserId): void { /* ... */ }
const uId = crearUserId(1);
const pId = 2 as ProductoId;
obtenerUsuario(uId); // OK
// obtenerUsuario(pId); // Error: ProductoId no es UserId
// obtenerUsuario(1); // Error: number no es UserId
10. Template Literal Types para validar strings
type EventoDOM = `on${Capitalize<string>}`;
// 'onClick', 'onChange', 'onSubmit', etc.
type HexColor = `#${string}`;
function setColor(color: HexColor): void { /* ... */ }
// setColor('#ff0000'); // OK
// setColor('red'); // Error
11-20. Más trucos rápidos
// 11. Parámetros de tipo con defaults (TS 5.5+):
type Respuesta<T = unknown> = { datos: T; ok: boolean };
// 12. using para cleanup automático (TS 5.2+):
// const { using db } = await abrirConexion(); // se cierra al salir del scope
// 13. Discriminated union con never para exhaustividad:
function assertNunca(x: never): never { throw new Error('No manejado: ' + x); }
// 14. infer en posición covariante para intersección:
type UnionToIntersection<U> =
(U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
// 15. keyof typeof para enums de objeto:
const Color = { Rojo: 'rojo', Azul: 'azul' } as const;
type Color = typeof Color[keyof typeof Color]; // 'rojo' | 'azul'
// 16. Mapped type con remap (as):
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
// 17. Conditional import para tipos pesados:
type LazyRouter = import('express').Router;
// 18. exactOptionalPropertyTypes en tsconfig:
// { nombre?: string } 'nombre' puede omitirse pero no ser undefined explícito
// 19. skipLibCheck: false en producción para detectar conflictos de tipos
// 20. verbatimModuleSyntax: fuerza import type para imports de solo tipo
Imagen: Pexels / Bibek Ghosh
