any y unknown aceptan cualquier valor. La diferencia está en lo que puedes hacer después con el valor: any te deja hacer cualquier cosa y no comprueba nada, mientras que unknown no te deja hacer casi nada hasta que demuestras qué es el valor.
Sin la línea @ts-expect-error, u.toUpperCase() es el error de compilación TS18046. La comprobación con typeof estrecha u a string, y dentro de ese bloque están disponibles todos los métodos de string.
any frente a unknown de un vistazo
any | unknown | |
|---|---|---|
| Acepta cualquier valor | sí | sí |
Asignarlo a un string, number, ... | sí, sin comprobar | no (TS2322) |
| Leer una propiedad, llamar a un método | sí, sin comprobar | no (TS18046) |
| Llamarlo como función | sí | no |
Aritmética y comparación (x * 2, x + 1, x < 5) | sí | no (TS18046) |
| Necesita una comprobación antes de usarlo | no | sí (typeof, instanceof, in, un type guard) |
| Efecto en la comprobación de tipos | desactivada para ese valor y todo lo que toca | se mantiene |
En términos de teoría de tipos, unknown es el tipo superior: cualquier tipo se le puede asignar, y él solo se puede asignar a unknown y a any. any es una escapatoria que se puede asignar en los dos sentidos, a todo excepto a never.
any desactiva la comprobación de tipos
Un valor de tipo any se acepta a ciegas. El compilador acepta erratas, tipos incorrectos y propiedades que faltan, y los errores aparecen en tiempo de ejecución.
La salida muestra el problema: una variable anotada como number contiene un string, y la última línea lanza Cannot read properties of undefined (reading 'city'). Además, any se propaga. user.name es any, así que cualquier valor calculado a partir de él es any, y un solo valor sin tipo puede desactivar la comprobación lejos de donde entró.
unknown te obliga a comprobar primero
Con unknown, el compilador rechaza cualquier operación hasta que el código estrecha el valor. El estrechamiento usa comprobaciones normales de JavaScript, y dentro de cada rama el valor tiene el tipo comprobado.
La comprobación value === null tiene que ir antes que la de objeto, porque typeof null es "object". Tras "id" in value, TypeScript sabe que el objeto tiene una propiedad id, de tipo unknown, porque nada dice qué contiene. String() la convierte en texto de forma explícita; para usarla como número tendrías que comprobarla antes con typeof.
También puedes saltarte la comprobación con una aserción, value as string, y el compilador la acepta. Es una promesa sin ninguna comprobación en tiempo de ejecución detrás, así que es preferible una comprobación real; consulta aserciones de tipo.
Validar JSON con unknown
JSON.parse está declarado para devolver any, así que su resultado desactiva la comprobación sin avisar. Anota el resultado como unknown y escribe un type guard que compruebe la forma antes de que el resto del código se fíe de ella.
La primera entrada imprime dark at 14px, la segunda se rechaza porque falta fontSize. Con any, la segunda entrada habría seguido adelante como un Settings con fontSize indefinido. Para esquemas grandes, una librería de validación hace el mismo trabajo y deriva el tipo por ti.
noImplicitAny
any no solo aparece cuando alguien lo escribe. Un parámetro sin anotación y sin contexto del que inferir también sería any. La opción noImplicitAny, que activa strict, lo informa como error:
index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.
La solución es una anotación, function double(x: number). Escribir x: any de forma explícita también compila, y esa es la idea: any sigue siendo visible en el código y se puede buscar y revisar.
Por dónde se sigue colando any
Incluso con strict, estas cosas producen any sin que la palabra aparezca en tu código:
| Origen | Qué obtienes | Qué hacer |
|---|---|---|
JSON.parse(text) | any | anotarlo como unknown y luego validar |
response.json() con los tipos del navegador (DOM) | Promise<any> | lo mismo que con JSON.parse (los tipos del propio fetch de Node ya devuelven Promise<unknown>) |
| Un paquete sin definiciones de tipos | any en sus imports (con un error salvo que añadas una declaración) | instalar @types/... o escribir un .d.ts |
value as any | any | usar un type guard o una aserción precisa |
catch (e) con useUnknownInCatchVariables desactivado | any | strict lo convierte en unknown; mantenlo así |
La variable de catch es unknown con strict porque se puede lanzar cualquier cosa, no solo objetos Error. Estréchala con e instanceof Error antes de leer e.message.
Cuándo es aceptable any
any no está prohibido, pero cada uno es un punto que el compilador deja de proteger. Usos razonables:
- Migrar un proyecto de JavaScript, donde
anymarca lo que todavía no está tipado. - Código que el sistema de tipos no puede expresar bien, pequeño y detrás de una firma de función tipada.
- Código de tests que pasa datos incorrectos a propósito.
Para todo lo demás, unknown cubre el mismo caso de «no conozco este tipo» manteniendo las comprobaciones. Muchos equipos lo imponen con la regla de lint @typescript-eslint/no-explicit-any. Un tipo relacionado, Record<string, unknown>, es la opción habitual para «un objeto con valores desconocidos».
Preguntas frecuentes
¿Qué diferencia hay entre any y unknown en TypeScript?
Los dos aceptan cualquier valor. Con any puedes hacer cualquier cosa con el valor (leer propiedades, llamarlo, asignarlo a un number) y el compilador no comprueba nada. Con unknown no puedes hacer casi nada hasta que lo estrechas con una comprobación como typeof x === "string". unknown mantiene activada la comprobación de tipos, y por eso es la opción más segura.
¿Cuándo debo usar unknown en lugar de any?
Siempre que el tipo de un valor no se conozca en tiempo de compilación: JSON parseado, datos de una respuesta de red, un error capturado, la entrada de una función de validación. Típalo como unknown y estréchalo. Recurre a any solo para trabajo de migración temporal o para código que el sistema de tipos no puede describir.
¿Qué significa "Object is of type 'unknown'"?
Los errores TS18046 ('x' is of type 'unknown') y TS2571 (Object is of type 'unknown', que aparece cuando el valor no es un simple nombre, como load().id) significan que usaste un valor unknown como si tuviera un tipo concreto, por ejemplo leyendo una propiedad o llamando a un método. Comprueba primero el tipo (typeof, instanceof, Array.isArray, in o una función type guard) y usa el valor dentro de la rama estrechada.
¿Por qué JSON.parse devuelve any?
Su declaración en la librería estándar dice parse(text: string, ...): any, porque el compilador no puede saber qué contiene un string. El resultado desactiva sin avisar la comprobación de todo lo que toca. Anótalo: const data: unknown = JSON.parse(text), y valídalo antes de usarlo.
¿Qué es noImplicitAny?
Una opción del compilador, parte de strict, que da un error cuando una declaración recibiría sin avisar el tipo any por no tener anotación ni nada de lo que inferir: TS7006 para un parámetro, TS7005 o TS7034 para una variable cuyo tipo no se puede deducir. Impide que aparezca any sin que nadie lo escriba. El any explícito sigue estando permitido.