20 tips de TypeScript para ser más productivo: satisfies, NoUnchecked, Extract y trucos del sistema de tipos

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

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP