TypeScript strict mode: las opciones que más importan

strict: true en tu tsconfig.json activa un conjunto de flags de TypeScript que capturan más errores en compile-time. Pero hay opciones fuera de ese conjunto que valen la pena activar por separado.

Las flags de strict: true

strict: true es un shorthand para activar estas flags simultáneamente:

{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "useUnknownInCatchVariables": true
  }
}

La más impactante es strictNullChecks. Sin ella, null y undefined son asignables a cualquier tipo, lo que permite bugs como:

function getUser(id: string): User {
  return db.find(id); // podría ser undefined, pero TS no lo sabe
}

const user = getUser('123');
console.log(user.name); // 💥 TypeError en runtime si no existe

Con strictNullChecks: true, TypeScript fuerza a manejar el caso undefined:

function getUser(id: string): User | undefined {
  return db.find(id);
}

const user = getUser('123');
// Error TS: object is possibly undefined
console.log(user?.name ?? 'Usuario desconocido');

noUncheckedIndexedAccess

Esta es la flag que más me gusta y que strict: true no activa. Cambia el tipo de accesos por índice a arrays y objetos para incluir undefined:

// Sin noUncheckedIndexedAccess
const items = ['a', 'b', 'c'];
const first: string = items[0]; // OK para TS, pero podría ser undefined

// Con noUncheckedIndexedAccess
const first: string | undefined = items[0]; // TypeScript te obliga a chequear

Parece molesto al principio. En la práctica, captura bugs reales:

function processFirst(items: string[]): string {
  // Error TS: Type 'string | undefined' is not assignable to 'string'
  return items[0].toUpperCase();
}

// Versión correcta:
function processFirst(items: string[]): string {
  const first = items[0];
  if (first === undefined) throw new Error('Lista vacía');
  return first.toUpperCase();
}

exactOptionalPropertyTypes

Diferencia entre una propiedad que no existe y una propiedad cuyo valor es undefined:

interface Config {
  timeout?: number;
}

// Sin exactOptionalPropertyTypes: ambas son equivalentes para TS
const a: Config = {};                   // timeout no existe
const b: Config = { timeout: undefined }; // timeout existe pero es undefined

// Con exactOptionalPropertyTypes: son distintas
const c: Config = { timeout: undefined }; // Error TS

Es especialmente útil cuando distinguís “no configurado” de “explícitamente vacío” en APIs.

Mi configuración base

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  }
}

noImplicitReturns obliga a que todas las ramas de una función devuelvan un valor. noFallthroughCasesInSwitch requiere break explícito en cada case. Ambas son ruidosas al principio y te ahorran bugs en producción.