JavaScript tiene tipado dinámico, lo cual es genuinamente útil — puedes iterar rápido, crear prototipos libremente
y desplegar sin un paso de compilación. Pero esa flexibilidad tiene un costo: toda una clase de errores
solo aparece en tiempo de ejecución. Una función espera una cadena, recibe undefined, y tu aplicación explota
en producción a las 2am. TypeScript
agrega un sistema de tipos estático encima de JavaScript que detecta exactamente esos errores en tiempo de compilación, en tu
editor, antes de que nada se despliegue. Este artículo explica lo que eso significa en la práctica — con código real,
compensaciones honestas, y sin exageraciones.
Qué es realmente TypeScript
TypeScript es un superconjunto de JavaScript: cada archivo .js válido también es válido
como .ts. Puedes renombrar un archivo, no agregar ninguna anotación de tipo, y se compilará sin problemas. Lo que TypeScript
agrega — anotaciones de tipos, interfaces, genéricos, enums — se elimina completamente en tiempo de compilación. El
resultado es JavaScript plano. TypeScript no cambia lo que se ejecuta; cambia lo que puedes saber
antes de que se ejecute.
El compilador es tsc, instalado vía
npm. Lo apuntas a tus
archivos .ts y produce archivos .js que tu runtime (Node.js, un navegador, Deno)
puede ejecutar. Ninguna sintaxis específica de TypeScript llega nunca a producción — los tipos solo existen para el
desarrollador y la cadena de herramientas. El
repositorio de TypeScript en GitHub
es en sí mismo un gran proyecto TypeScript, lo que te da una idea de cómo escala.
# Compile a single file
npx tsc src/index.ts
# Compile a whole project (uses tsconfig.json)
npx tsc
# Watch mode — recompile on every save
npx tsc --watchEl beneficio principal: errores antes del tiempo de ejecución
Aquí está el problema que TypeScript resuelve de manera más visible. Imagina que estás trabajando con una respuesta de API
donde un campo podría ser una cadena o null. En JavaScript plano, esto falla silenciosamente en
producción:
// JavaScript — no error until this actually runs with a null value
function formatDisplayName(user) {
return user.displayName.toUpperCase(); // 💥 TypeError if displayName is null
}
// This looks fine in isolation. The bug only appears when a user
// has no display name set — which might be rare, but it will happen.
const name = formatDisplayName({ displayName: null });TypeScript detecta esto antes de que el código se ejecute:
interface User {
id: number;
displayName: string | null;
email: string;
}
function formatDisplayName(user: User): string {
return user.displayName.toUpperCase();
// ❌ TypeScript error: Object is possibly 'null'.
// Property 'toUpperCase' does not exist on type 'null'.
}
// Fix: handle the null case explicitly
function formatDisplayName(user: User): string {
if (user.displayName === null) {
return user.email; // fall back to email
}
return user.displayName.toUpperCase(); // TypeScript now knows this is safe
}Este es el momento «aha» para la mayoría de los desarrolladores. El mensaje de error te dice exactamente qué está mal
y dónde — en tu editor, antes de ejecutar una sola línea. Con el modo strict habilitado, TypeScript
es particularmente agresivo para detectar problemas con null y undefined a través de su
función strictNullChecks,
que está activada por defecto en modo strict.
tsc significa que tu código tiene una inconsistencia de tipo — TypeScript puede probar que fallará
(o podría fallar) sin ejecutarlo. Corrige el error de tipo, y habrás eliminado esa clase de bug por completo.El sistema de tipos de TypeScript de un vistazo
No necesitas anotar todo. TypeScript infiere tipos de asignaciones, valores de retorno y llamadas a funciones — a menudo solo escribes JavaScript normal y obtienes verificación de tipos gratis. Pero conocer la sintaxis de anotación ayuda cuando la inferencia no es suficiente.
// Primitives
const productName: string = 'Wireless Keyboard';
const price: number = 79.99;
const inStock: boolean = true;
// TypeScript infers these without annotations — same effect
const productName = 'Wireless Keyboard'; // inferred: string
const price = 79.99; // inferred: number
// Arrays
const tags: string[] = ['electronics', 'peripherals'];
const ratings: number[] = [4.5, 4.8, 4.2];
// Objects with interfaces
interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
description: string | null; // union type — string or null
}
// Union types — a value that could be one of several types
type Status = 'active' | 'inactive' | 'pending'; // string literal union
type ID = string | number; // string or number
// Function with typed parameters and return type
function formatProductCard(product: Product): string {
const stockLabel = product.inStock ? 'In Stock' : 'Out of Stock';
const desc = product.description ?? 'No description available';
return `${product.name} — $${product.price.toFixed(2)} (${stockLabel})
${desc}`;
}
// Generic function — works with any type, preserves it
function firstOrDefault<T>(items: T[], fallback: T): T {
return items.length > 0 ? items[0] : fallback;
}
const first = firstOrDefault(['a', 'b', 'c'], 'z'); // inferred: string
const num = firstOrDefault([1, 2, 3], 0); // inferred: numberEl manual de TypeScript profundiza en el sistema de tipos — está genuinamente bien escrito y vale la pena leerlo una vez que tengas los fundamentos.
Configurar TypeScript en un proyecto
Agregar TypeScript a un proyecto toma unos cinco minutos. Instalas el compilador como dependencia de desarrollo,
generas un archivo de configuración y comienzas a renombrar archivos .js a .ts al
ritmo que tenga sentido.
# Install TypeScript as a dev dependency
npm install -D typescript
# Generate tsconfig.json with sensible defaults
npx tsc --initEl tsconfig.json generado tiene docenas de opciones, la mayoría comentadas. Las principales
que debes conocer:
{
"compilerOptions": {
"target": "ES2020", // what JavaScript version to output
"module": "commonjs", // module system (commonjs for Node, ESNext for bundlers)
"outDir": "./dist", // where compiled .js files go
"rootDir": "./src", // where your .ts source files live
"strict": true, // enables all strict type checks — highly recommended
"esModuleInterop": true, // smoother interop with CommonJS modules
"skipLibCheck": true // skip type checks on .d.ts files in node_modules
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}Siempre habilita strict: true en un proyecto nuevo. Agrupa varias
verificaciones incluyendo strictNullChecks, noImplicitAny y
strictFunctionTypes. En una base de código existente puede que necesites habilitarlas gradualmente —
pero para cualquier cosa greenfield, empieza en modo strict.
TypeScript vs JavaScript — las compensaciones reales
TypeScript tiene muchas ventajas genuinas, pero también costos reales. Aquí hay un desglose honesto:
- Autocompletado que realmente funciona. Tu editor conoce la forma de cada objeto, por lo que puede sugerir los nombres de propiedades correctos y las firmas de métodos en lugar de adivinar. Esto solo ahorra tiempo todos los días.
- Bugs detectados antes de llegar a producción. Desreferencias null, tipos de argumentos incorrectos, propiedades requeridas faltantes — TypeScript los detecta al escribir, no a las 3am en un canal de incidentes.
- Refactorización más segura. Renombra un campo en una interfaz y TypeScript inmediatamente señala todos los sitios de llamada que necesitan actualización. En una gran base de código JavaScript, ese tipo de cambio es aterrador.
- El código se documenta a sí mismo. Una firma de función como
sendEmail(to: string, subject: string, body: string, options?: EmailOptions): Promise<SendResult>te dice todo lo que necesitas saber sin leer la implementación. - Mejor para equipos. Cuando múltiples desarrolladores comparten una base de código, los tipos forman un contrato entre módulos. Puedes refactorizar tu código sin romper el de tu colega.
Y los costos honestos:
- Agrega un paso de compilación. Para un pequeño script o un prototipo rápido, ejecutar
tscantes de poder probar es fricción real. El JavaScript vanilla se ejecuta inmediatamente. - La configuración inicial toma tiempo. Configurar
tsconfig.json, agregar definiciones de tipos para bibliotecas de terceros (@types/express, etc.) y configurar tu editor correctamente es algunas horas de trabajo. anysocava todo. TypeScript tiene una vía de escape — puedes tipear cualquier cosa comoany, lo que deshabilita la verificación de tipos para ese valor. El abuso deanysignifica que tienes la fricción de TypeScript sin la seguridad. Es una muleta, no una solución.- Los tipos de bibliotecas de terceros pueden quedarse atrás. No todos los paquetes npm incluyen definiciones TypeScript.
Algunos dependen de paquetes
@types/*mantenidos por la comunidad que pueden estar incompletos o desactualizados.
Donde TypeScript realmente brilla
TypeScript paga los mayores dividendos en tres situaciones: bases de código grandes, bibliotecas compartidas, y cualquier cosa que será refactorizada con el tiempo. El sistema de tipos actúa como documentación viva que siempre está actualizada — a diferencia de los comentarios o archivos README.
Un ejemplo práctico: tu aplicación llama a una API REST que devuelve datos de usuario. Sin TypeScript, confías en que la API devuelva lo que esperas. Con TypeScript, modelas la respuesta y obtienes retroalimentación inmediata si algo no coincide:
interface ApiUser {
id: number;
username: string;
email: string;
avatarUrl: string | null;
createdAt: string; // ISO 8601 date string
role: 'admin' | 'editor' | 'viewer';
}
interface ApiResponse<T> {
data: T;
total: number;
page: number;
perPage: number;
}
async function fetchUsers(page: number): Promise<ApiResponse<ApiUser[]>> {
const response = await fetch(`/api/users?page=${page}`);
if (!response.ok) {
throw new Error(`Failed to fetch users: ${response.status}`);
}
return response.json() as Promise<ApiResponse<ApiUser[]>>;
}
// Now every consumer of this function knows exactly what they'll get
const result = await fetchUsers(1);
result.data.forEach(user => {
// TypeScript knows user.role is 'admin' | 'editor' | 'viewer'
// It will error if you try to access a property that doesn't exist
console.log(`${user.username} (${user.role})`);
});Cuando necesitas desactivar temporalmente la verificación de tipos, usa unknown en lugar de
any. La diferencia: any silenciosamente evita todas las verificaciones, mientras que unknown
te obliga a reducir el tipo antes de usar el valor — obtienes la vía de escape sin perder la seguridad
por completo.
// ❌ any — TypeScript trusts you completely, no checks
function processData(data: any) {
data.nonExistentMethod(); // no error — TypeScript looks away
}
// ✅ unknown — you have to prove what it is before using it
function processData(data: unknown) {
if (typeof data === 'string') {
console.log(data.toUpperCase()); // safe — TypeScript knows it's a string here
} else if (Array.isArray(data)) {
console.log(data.length); // safe — TypeScript knows it's an array
}
}El Playground de TypeScript es la forma más rápida de experimentar con estos patrones. Pega código, ve la salida JavaScript compilada y cualquier error de tipo en tiempo real — sin necesidad de instalación.
Conclusión
TypeScript no es magia, y no es correcto para todos los proyectos. Pero para cualquier base de código JavaScript que esté creciendo, siendo mantenida por más de una persona, o que será refactorizada — genuinamente cambia la experiencia de desarrollo. El sistema de tipos detecta toda una categoría de bugs de tiempo de ejecución antes de que se desplieguen, hace que el autocompletado sea realmente útil, y convierte la refactorización de un proceso manual arriesgado en algo que maneja la cadena de herramientas.
Empieza con la documentación oficial de TypeScript — está bien estructurada y es amigable para principiantes. El Playground de TypeScript es excelente para experimentar sin configuración. La entrada del glosario TypeScript de MDN es una buena descripción general de una página si quieres una segunda perspectiva. Y si estás trabajando con datos JSON en TypeScript, la herramienta JSON a TypeScript en este sitio genera interfaces TypeScript directamente desde cualquier carga útil JSON — útil cuando necesitas modelar una respuesta de API rápidamente sin escribir los tipos a mano.