← React & TypeScript

Fundamentos de TypeScript

¿Cuál es la diferencia entre `any`, `unknown` y `never`? ¿Cuándo usarías cada uno?

Ver respuesta — intenta responderla en voz alta primero

any desactiva el chequeo de tipos: puedes hacer cualquier cosa y el compilador no dice nada. unknown es lo contrario: acepta cualquier valor, pero te obliga a verificar su tipo antes de usarlo. never representa algo que no debería ocurrir nunca: funciones que no retornan o ramas de código imposibles.

Los tres suenan parecidos pero cumplen roles opuestos:

  • any: apaga el compilador para ese valor. Puedes llamar métodos inexistentes y TypeScript no protesta. Además se contagia: todo lo que sale de una expresión any también es any, y el agujero de tipos se extiende por el código.
  • unknown: el "any seguro". Sirve para datos cuyo tipo no conoces todavía (respuestas de API, JSON.parse, catch). No puedes hacer nada con él hasta que hagas narrowing con typeof, instanceof o una validación.
  • never: el tipo de "esto no debería pasar". Aparece en funciones que siempre lanzan un error y en la comprobación de exhaustividad de un switch. Ningún valor puede asignarse a never, y eso es justo lo que lo hace útil.

Regla práctica: si te ves escribiendo any, casi siempre lo que quieres es unknown más una verificación.

// any: el compilador se apaga y el error llega en runtime
const datoAny: any = "hola";
datoAny.toFixed(2); // compila... y explota en runtime
const contagiado = datoAny + 1; // contagiado también es any

// unknown: acepta todo, pero exige verificar antes de usar
function procesarEntrada(entrada: unknown): string {
  // entrada.toUpperCase();
  // Error: 'entrada' is of type 'unknown'.

  if (typeof entrada === "string") {
    return entrada.toUpperCase(); // ya verificamos: aquí es string
  }
  if (typeof entrada === "number") {
    return entrada.toFixed(2);
  }
  return "tipo no soportado";
}

// Caso real de unknown: JSON.parse devuelve any, mejor tratarlo como unknown
const crudo: unknown = JSON.parse('{"nombre":"Ana"}');

// never: funciones que jamás retornan
function fallar(mensaje: string): never {
  throw new Error(mensaje);
}

// never en exhaustividad: garantiza que cubrimos todos los casos
type Rol = "admin" | "usuario";

function describirRol(rol: Rol): string {
  switch (rol) {
    case "admin": return "Acceso total";
    case "usuario": return "Acceso limitado";
    default: {
      const nunca: never = rol; // si Rol crece, esto deja de compilar
      return nunca;
    }
  }
}

Resolver un error de tipos con any "para que compile":

// El junior hace esto para silenciar el error:
const respuesta: any = await fetch("/api/usuario").then((r) => r.json());
console.log(respuesta.nombre.toUpperCase()); // puede explotar en runtime

// Mejor: unknown + verificación
const datos: unknown = await fetch("/api/usuario").then((r) => r.json());
if (typeof datos === "object" && datos !== null && "nombre" in datos) {
  console.log(datos.nombre); // acceso seguro tras el narrowing
}

Con any el error no desaparece: solo se muda de la compilación al runtime, que es el peor lugar.

"any desactiva el chequeo de tipos por completo y además se contagia a todo lo que toca, así que evito usarlo. unknown es la alternativa segura: también acepta cualquier valor, pero el compilador me obliga a verificar el tipo antes de usarlo; es ideal para respuestas de API o JSON.parse. Y never representa lo imposible: el retorno de una función que siempre lanza un error, o el default de un switch exhaustivo, donde asigno el valor a never para que el compilador me avise si aparece un caso sin manejar."

Reto rápido

Escribe longitud(valor: unknown): number que devuelva la longitud si valor es un string o un arreglo, y 0 en cualquier otro caso. Sin usar any ni as.

Ver solución
function longitud(valor: unknown): number {
  if (typeof valor === "string") {
    return valor.length; // narrowing a string
  }
  if (Array.isArray(valor)) {
    return valor.length; // narrowing a arreglo
  }
  return 0;
}

longitud("hola");      // 4
longitud([1, 2, 3]);   // 3
longitud(42);          // 0