🇺🇸 EN
TypeScript
·7 min read·↗·🇺🇸 EN

Por qué deberías evitar usar 'any' en TypeScript

Portada principal del artículo: Por qué deberías evitar usar 'any' en TypeScript

Cuando creamos aplicaciones en Angular, el tipo any parece nuestro "salvavidas" cuando lidiamos con mensajes de error complejos, cuando queremos ahorrar tiempo sin escribir definiciones de tipos específicas, o cuando pensamos que la comprobación de tipos de TypeScript limita nuestra flexibilidad al programar.

Usar el tipo any puede parecer una solución rápida para problemas comunes, pero es importante reflexionar sobre los problemas ocultos y el impacto real que tiene tomar este atajo.

Usar any de forma frecuente debilita el propósito principal de TypeScript, que es hacer nuestro código más seguro y detectar errores a tiempo. Ignorar las ventajas del sistema de tipos genera errores difíciles de rastrear, además de código más complejo y difícil de mantener.

Hoy quiero compartirte varias razones, con ejemplos prácticos, de por qué evito usar any y elijo unknown en su lugar. También veremos cómo apoyarnos en los tipos de utilidad de TypeScript para crear tipos flexibles sin perder control. Empecemos.

Seguridad de tipos

Elegir el tipo any parece una solución sencilla cuando surgen problemas de tipado. Sin embargo, esta decisión tiene una gran desventaja: perdemos la seguridad de tipos, que es la característica clave de TypeScript para garantizar que el código funcione bien.

Quiero mostrarte los riesgos de ignorar el tipado estricto al usar any, y recordar por qué el sistema de tipos de TypeScript es tan valioso en nuestro día a día.

Mira el siguiente ejemplo. Tenemos el método accountBalance, que recibe un objeto account y un valor amount para actualizar el balance:

export class Accounting {
 
  accountBalance(account: any, amount:any) {
    return account.balance += amount;
  }
}

Como el método accountBalance espera el tipo any tanto para account como para amount, puedo pasar los siguientes parámetros sin que el compilador se queje:

let accounting = new Accounting()
let balanceA = accounting.accountBalance({accountNumber: '05654613', balance: 15}, 26)    
let balanceB = accounting.accountBalance({accountNumber: '05654613', balance: 15}, '26')
console.log({balanceA, balanceB})

¿Es este el resultado esperado? ¿Por qué todo compila sin problemas a simple vista, pero falla en tiempo de ejecución?

Cuando trabajamos con TypeScript, esperamos que el editor o el compilador nos adviertan sobre este tipo de problemas 🤦‍♂️.

¿Por qué el compilador no nos avisa? Porque le dijimos que acepte cualquier cosa. La solución más directa y limpia es definir un tipo Account explícito:

export type Account = {
  accountNumber: string;
  balance: number;
}

Cambiamos la firma de la función accountBalance para usar el tipo Account:

 accountBalance(account: Account, amount: number): number {
    return (account.total += amount);
  }

Con los tipos adecuados, el editor, el compilador y la aplicación nos avisan de inmediato sobre los posibles fallos.

¡Listo! Ahora sabemos exactamente cómo usar la función y evitamos fallos silenciosos en tiempo de ejecución. Pero la seguridad de tipos no solo previene errores, también transforma nuestra experiencia con las herramientas de desarrollo. Veamos qué pasa en el editor.

El editor: WebStorm y VS Code

Nos encanta TypeScript porque hace mucho más que revisar tipos: mejora Visual Studio Code y WebStorm con herramientas potentes como autocompletado, navegación en el código y refactorización automática. Todas estas funciones dependen directamente del sistema de tipos.

Pero cuando usamos any, perdemos tanto la seguridad como la ayuda de estas herramientas. Vamos a ver cómo abusar de any empeora la experiencia al programar, y cómo un buen tipado hace que refactorizar sea un proceso seguro y predecible.

Por ejemplo, supongamos que tenemos un método llamado updateAccount que recibe una cuenta con tipo any, además de un objeto inicial vacío:

account: any = {}
  DEFAULT_BALANCE = 3000
 
  updateAccount(account: any) {
    account.accountID = Math.random().toString();
    account.balance = this.DEFAULT_BALANCE;
    return account;
  }

A primera vista todo parece funcionar bien. Pero, ¿qué pasa si quiero refactorizar 'accountNumber' a 'id' y 'balance' a 'total'?

Si uso las herramientas de refactorización de WebStorm para cambiar los nombres de las propiedades, al cambiar 'accountID' a 'id' y 'balance' a 'total', el editor no logra hacer todos los ajustes necesarios:

Podemos solucionar este problema cambiando el tipo de any a Account. Con ese cambio, podemos hacer la refactorización con total confianza:

updateAccount(account: Account): Account {
    account.id = Math.random().toString();
    account.total = this.DEFAULT_BALANCE;
    return account;
  }

Ahora volvemos a refactorizar y el editor detecta todas las apariciones, logrando un proceso limpio y sin sorpresas.

¿Y qué pasa con el objeto account? En lugar de usar any, vamos a cambiarlo a Account. El editor nos avisará de inmediato que el objeto no está inicializado y que le faltan todas las propiedades requeridas por Account.

Así, tanto el editor como el compilador nos obligan a inicializar todas las propiedades necesarias:

DEFAULT_BALANCE = 3000;
  account: Account = {
    id: "DEFAULT_ID",
    total : this.DEFAULT_BALANCE
  }

Como puedes ver, usar any puede ahorrar algo de tiempo al principio, pero en muchas ocasiones el precio que pagamos después no vale la pena.

¿Qué podemos hacer?

Quizás te preguntes: ¿qué alternativas tenemos cuando realmente necesitamos flexibilidad? En esos casos, el tipo unknown es tu mejor aliado. Pero antes de presentarlo junto con los tipos de utilidad, veamos las diferencias clave.

Antes de ver unknown, revisemos la diferencia principal entre any y unknown:

  • any permite cualquier operación sin validar nada, lo que puede causar errores en tiempo de ejecución.
  • unknown exige una validación de tipo explícita antes de usarlo.

El tipo Any

El tipo any te permite ejecutar cualquier acción sin comprobación de tipos, haciendo que TypeScript sea más permisivo. Aunque esto puede parecer útil, también es muy riesgoso, ya que suele provocar errores difíciles de encontrar durante la ejecución de la aplicación.

Ejemplo:

let riskyData: any = getDataFromAPI();
console.log(riskyData.name); // Compila sin problemas, incluso si riskyData no tiene la propiedad name.
riskyData(); // Compila sin problemas, incluso si riskyData no es una función.

En este ejemplo, TypeScript no muestra ninguna advertencia sobre riskyData porque tiene el tipo any. Esto puede generar errores en tiempo de ejecución si riskyData no tiene las propiedades o métodos que esperamos.

El tipo Unknown

El tipo unknown es mucho más seguro que any. Puede representar cualquier valor, pero restringe las operaciones directas sobre él. Para usar un valor unknown, necesitas realizar comprobaciones específicas para confirmar su tipo.

Ejemplo:

let safeData: unknown = getDataFromAPI();
console.log(safeData.name); // Error: Object is of type 'unknown'.
if (typeof safeData === 'string') {
   // Podemos ejecutar esto porque TypeScript ya sabe que es un string.
    console.log(safeData.toUpperCase()); 
} else if (typeof safeData === 'object' && safeData !== null && 'name' in safeData) {
   // Seguro, ya que comprobamos que 'name' es una propiedad en safeData.
    console.log(safeData.name); 
}

En este ejemplo, TypeScript se asegura de que solo accedas a la propiedad name o transformes safeData a mayúsculas después de validar su tipo. Si quieres ver más patrones de estrechamiento de tipos como guardas personalizadas y validación con esquemas, puedes leer mi artículo sobre cómo comprobar tipos en TypeScript. Esto ayuda a prevenir errores asegurando que las operaciones sobre los datos sean seguras.

Cuando da pereza crear tipos

Entiendo que a veces solo queremos crear un tipo con unas pocas propiedades, o reutilizar Account omitiendo algún campo. Para estas situaciones, los tipos de utilidad de TypeScript son de gran ayuda.

Estos tipos de utilidad te permiten crear tipos derivados de otros. Los dos que más utilizo para evitar el uso de any son Pick y Omit:

  1. Pick: Crea un tipo seleccionando propiedades específicas de otro tipo. Ejemplo: type AccountID = Pick<Account, 'id'>;
  2. Omit: Genera un tipo excluyendo propiedades específicas. Ejemplo: type AccountWithoutID = Omit<Account, 'id'>;

Resumen

Sé que usar any puede ser tentador, pero debemos tratarlo con mucha precaución. Ahora que entendemos las consecuencias y el costo que implica, utilizar alternativas como unknown y los tipos de utilidad te hará la vida más fácil y dará como resultado una base de código mucho más sólida.

![](https://media.licdn.com/dms/image/D4D2CAQGNZI_1ByQ3HQ/comment-image-shrink_8192_1280/0/1701106468786?e=1701716400&v=beta&t=uXQx78tdLesWluJaBsCzQEjyMRmBAR9vbAbUwsbRw20 align="center")

Si quieres aprender más sobre tipos, no dudes en revisar estos otros artículos:

Foto por Brett Jordan en Unsplash

Parte de la serie TypeScript

Mastering the language of the web. From basic types to advanced generics and patterns.

Ver serie completa

Preguntas Frecuentes

¿Por qué se considera una mala práctica usar el tipo any en TypeScript?

Usar any desactiva el verificador de tipos por completo, convirtiendo errores de compilación en fallos en tiempo de ejecución, rompiendo las herramientas de refactorización del IDE y eliminando el autocompletado.

¿Cuál es la diferencia entre any y unknown en TypeScript?

Tanto any como unknown aceptan cualquier valor, pero unknown requiere una verificación de tipos explícita (mediante typeof, instanceof o guardas de tipo personalizadas) antes de poder llamar métodos o acceder a sus propiedades.

¿Cómo eliminan el uso de any los tipos de utilidad como Pick, Omit y Partial?

Los tipos de utilidad integrados te permiten derivar nuevas estructuras a partir de interfaces existentes sin repetir código ni recurrir a any para objetos parciales o modificados.

¿Cómo ayuda el operador satisfies a mantener tipos estrictos en TypeScript?

El operador satisfies valida que una expresión coincida con un contrato de tipo sin ampliar el tipo resultante, preservando la inferencia exacta de literales y propiedades.

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.