Volver al blog

TypeScript en 2025: Por Qué 38.5% de los Desarrolladores Eligieron y Tú También Deberías

Hola HaWkers, TypeScript está en 38.5% de popularidad según encuestas de 2025, consolidándose como uno de los top 5 lenguajes de programación del mundo. Pero ¿será que es solo hype o hay razones sólidas para adoptar?

En este artículo, vamos a explorar por qué TypeScript dominó el ecosistema JavaScript, cómo comenzar de forma práctica y estrategias de migración para proyectos existentes.

Por Qué TypeScript Venció la Batalla

1. Seguridad de Tipos Previene Bugs Caros

// ❌ JavaScript: Bug solo aparece en producción
function calculateDiscount(price, percentage) {
  return price - (price * percentage / 100);
}

// Bug: alguien pasa string por error
calculateDiscount('100', 20); // '100-NaN' = NaN 😱

// ✅ TypeScript: Error detectado en tiempo de desarrollo
function calculateDiscount(price: number, percentage: number): number {
  return price - (price * percentage / 100);
}

// Error en tiempo de compilación!
calculateDiscount('100', 20);
// Error: Argument of type 'string' is not assignable to parameter of type 'number'

2. IntelliSense y Autocomplete Poderosos

// TypeScript proporciona autocomplete preciso
interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
  createdAt: Date;
  metadata?: {
    lastLogin?: Date;
    preferences: {
      theme: 'light' | 'dark';
      notifications: boolean;
    };
  };
}

function greetUser(user: User) {
  // IDE sugiere todas propiedades disponibles
  console.log(`Hello, ${user.name}!`);

  // Autocomplete en objetos anidados
  if (user.metadata?.preferences.theme === 'dark') {
    // IDE sabe exactamente lo que está disponible
    console.log('Dark mode enabled');
  }

  // Tipos literales previenen typos
  if (user.role === 'admim') { // Error: 'admim' no existe
    // ...
  }
}

3. Refactoring Seguro en Grandes Codebases

// Escenario: Renombrar propiedad en 100 archivos

// Interface original
interface Product {
  productName: string;
  price: number;
}

// Refactorizar para 'name' en vez de 'productName'
interface Product {
  name: string; // TypeScript muestra TODOS los lugares que rompieron
  price: number;
}

// Todos los usos son automáticamente identificados
function displayProduct(product: Product) {
  return `${product.productName} - $${product.price}`;
  // Error: Property 'productName' does not exist on type 'Product'
  // Sugerencia: Did you mean 'name'?
}

// En JavaScript: Descubrirías ese bug en producción 💥

TypeScript en la Práctica: Del Básico al Avanzado

Tipos Primitivos y Básicos

// Tipos básicos
let isDone: boolean = false;
let count: number = 42;
let name: string = 'John';
let notSure: any = 4; // ¡Evita usar 'any'!

// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];

// Tuplas (arrays con tipos fijos)
let tuple: [string, number] = ['age', 30];

// Enum
enum Status {
  Pending = 'PENDING',
  Approved = 'APPROVED',
  Rejected = 'REJECTED'
}

const orderStatus: Status = Status.Pending;

// Union Types (tipos alternativos)
let id: string | number;
id = '123'; // OK
id = 123;   // OK
id = true;  // Error

// Literal Types (valores específicos)
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

function makeRequest(url: string, method: HttpMethod) {
  // method solo puede ser uno de los valores especificados
}

makeRequest('/api/users', 'GET');     // OK
makeRequest('/api/users', 'PATCH');   // Error

Interfaces y Type Aliases

// Interface: Define estructura de objetos
interface User {
  id: string;
  name: string;
  email: string;
  age?: number; // Opcional
  readonly createdAt: Date; // Readonly
}

// Extending interfaces
interface AdminUser extends User {
  permissions: string[];
  role: 'admin';
}

// Type Alias: Similar a interface, pero más flexible
type Point = {
  x: number;
  y: number;
};

// Type para union types
type ID = string | number;

// Type para funciones
type GreetFunction = (name: string) => string;

const greet: GreetFunction = (name) => `Hello, ${name}!`;

// Interface vs Type: ¿Cuándo usar?
// Usa Interface para objetos que pueden ser extendidos
// Usa Type para unions, primitivos, tuplas

// Intersection Types (combinar tipos)
type WithTimestamp = {
  createdAt: Date;
  updatedAt: Date;
};

type UserWithTimestamp = User & WithTimestamp;

const user: UserWithTimestamp = {
  id: '1',
  name: 'John',
  email: 'john@example.com',
  createdAt: new Date(),
  updatedAt: new Date()
};

Generics: Tipos Reutilizables

// Generic simple
function identity<T>(arg: T): T {
  return arg;
}

const num = identity<number>(42);
const str = identity<string>('hello');

// Generic con constraints
interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(arg: T): T {
  console.log(arg.length); // OK, T tiene length
  return arg;
}

logLength('hello');     // OK, string tiene length
logLength([1, 2, 3]);   // OK, array tiene length
logLength(42);          // Error, number no tiene length

// Generic en interfaces
interface Response<T> {
  data: T;
  status: number;
  message: string;
}

interface User {
  id: string;
  name: string;
}

const userResponse: Response<User> = {
  data: { id: '1', name: 'John' },
  status: 200,
  message: 'Success'
};

const usersResponse: Response<User[]> = {
  data: [
    { id: '1', name: 'John' },
    { id: '2', name: 'Jane' }
  ],
  status: 200,
  message: 'Success'
};

// Uso práctico de utility types
interface TodoItem {
  id: string;
  title: string;
  completed: boolean;
  createdAt: Date;
}

// Partial: Todos campos opcionales
type TodoUpdate = Partial<TodoItem>;

function updateTodo(id: string, updates: TodoUpdate) {
  // Puede pasar solo los campos que quieres actualizar
}

updateTodo('1', { completed: true }); // OK
updateTodo('2', { title: 'New title', completed: false }); // OK

// Pick: Seleccionar solo campos específicos
type TodoSummary = Pick<TodoItem, 'id' | 'title'>;

const summary: TodoSummary = {
  id: '1',
  title: 'Task'
  // completed y createdAt no son necesarios
};

Casos de Uso Avanzados

// 1. Discriminated Unions (Pattern Matching)
type Success<T> = {
  type: 'success';
  data: T;
};

type Error = {
  type: 'error';
  error: string;
};

type Result<T> = Success<T> | Error;

function handleResult<T>(result: Result<T>) {
  // TypeScript sabe exactamente cuál tipo basado en 'type'
  if (result.type === 'success') {
    console.log(result.data); // OK, TypeScript sabe que tiene 'data'
  } else {
    console.log(result.error); // OK, TypeScript sabe que tiene 'error'
  }
}

// 2. Conditional Types
type IsString<T> = T extends string ? 'yes' : 'no';

type A = IsString<string>;  // 'yes'
type B = IsString<number>;  // 'no'

// 3. Mapped Types
type Optional<T> = {
  [K in keyof T]?: T[K];
};

type RequiredUser = {
  name: string;
  email: string;
  age: number;
};

type OptionalUser = Optional<RequiredUser>;
// { name?: string; email?: string; age?: number; }

// 4. Template Literal Types (TypeScript 4.1+)
type HTTPMethod = 'GET' | 'POST' | 'PUT';
type Route = '/users' | '/products' | '/orders';

type APIEndpoint = `${HTTPMethod} ${Route}`;
// 'GET /users' | 'GET /products' | 'GET /orders' |
// 'POST /users' | 'POST /products' | 'POST /orders' |
// 'PUT /users' | 'PUT /products' | 'PUT /orders'

// 5. Type Guards
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function processValue(value: unknown) {
  if (isString(value)) {
    console.log(value.toUpperCase()); // OK, TypeScript sabe que es string
  }
}

Migración Gradual de JavaScript para TypeScript

Estrategia de Migración

// 1. Configuración inicial (tsconfig.json)
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM"],
    "jsx": "react-jsx",
    "strict": false, // Comenzar con false, habilitar gradualmente
    "allowJs": true, // Permitir archivos .js durante migración
    "checkJs": false, // No checar archivos .js inicialmente
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

// 2. Plan de migración
const migrationPlan = {
  'Fase 1 - Setup': [
    'Instalar TypeScript y tipos',
    'Configurar tsconfig.json',
    'Renombrar 1 archivo .js → .ts para testar',
    'Configurar build pipeline'
  ],
  'Fase 2 - Archivos Nuevos': [
    'Todos archivos nuevos en TypeScript',
    'Crear types.d.ts para interfaces comunes'
  ],
  'Fase 3 - Migración Gradual': [
    'Migrar módulos utilitarios primero',
    'Migrar componentes independientes',
    'Migrar componentes con dependencias',
    'Habilitar strict mode gradualmente'
  ],
  'Fase 4 - Strictness': [
    'Habilitar noImplicitAny',
    'Habilitar strictNullChecks',
    'Habilitar strictFunctionTypes',
    'Habilitar strictPropertyInitialization'
  ]
};

TypeScript con Frameworks Populares

React + TypeScript

import React, { useState, useEffect } from 'react';

// Props tipadas
interface UserCardProps {
  user: {
    id: string;
    name: string;
    email: string;
    avatar?: string;
  };
  onEdit?: (userId: string) => void;
  onDelete?: (userId: string) => void;
}

// Componente funcional tipado
export function UserCard({ user, onEdit, onDelete }: UserCardProps) {
  const [isHovered, setIsHovered] = useState<boolean>(false);

  useEffect(() => {
    console.log('User card mounted:', user.id);

    return () => {
      console.log('User card unmounted:', user.id);
    };
  }, [user.id]);

  const handleEdit = () => {
    onEdit?.(user.id); // Safe navigation operator
  };

  return (
    <div
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      className={isHovered ? 'card-hovered' : 'card'}
    >
      {user.avatar && <img src={user.avatar} alt={user.name} />}
      <h3>{user.name}</h3>
      <p>{user.email}</p>

      <button onClick={handleEdit}>Edit</button>
      {onDelete && (
        <button onClick={() => onDelete(user.id)}>Delete</button>
      )}
    </div>
  );
}

// Hooks customizados tipados
function useUser(userId: string) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let cancelled = false;

    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();

        if (!cancelled) {
          setUser(data);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err as Error);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }

    fetchUser();

    return () => {
      cancelled = true;
    };
  }, [userId]);

  return { user, loading, error };
}

Next.js + TypeScript

// app/page.tsx (App Router)
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Home Page',
  description: 'Welcome to my site'
};

interface HomePageProps {
  searchParams: { [key: string]: string | string[] | undefined };
}

export default async function HomePage({ searchParams }: HomePageProps) {
  // Server component tipado
  const data = await fetchData();

  return (
    <div>
      <h1>Welcome</h1>
      <DataDisplay data={data} />
    </div>
  );
}

// API Route tipada
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';

interface UserPayload {
  name: string;
  email: string;
}

export async function POST(request: NextRequest) {
  try {
    const body: UserPayload = await request.json();

    // Validación
    if (!body.name || !body.email) {
      return NextResponse.json(
        { error: 'Missing required fields' },
        { status: 400 }
      );
    }

    // Crear usuario
    const user = await createUser(body);

    return NextResponse.json(user, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

Conclusión: ¿Vale la Pena Invertir en TypeScript?

Sí, absolutamente. TypeScript no es más una tendencia — es el nuevo estándar para JavaScript profesional en 2025.

Beneficios concretos:

  • Menos bugs: Errores detectados antes de producción
  • Mejor DX: Autocomplete y refactoring seguros
  • Código más mantenible: Documentación viva via tipos
  • Carrera: 68% de las vacantes exigen TypeScript

Si quieres dominar JavaScript y TypeScript de forma estructurada, recomiendo que des una mirada a otro artículo: Programación Funcional en JavaScript: Entendiendo Higher-Order Functions donde vas a descubrir patrones que funcionan perfectamente con TypeScript.

¡Vamos a por ello! 🦅

📚 ¿Quieres Profundizar Tus Conocimientos en JavaScript?

Este artículo cubrió TypeScript, pero dominar JavaScript sólido es la base para todo.

Desarrolladores que invierten en conocimiento estructurado tienden a tener más oportunidades en el mercado.

Material de Estudio Completo

Si quieres dominar JavaScript del básico al avanzado, preparé una guía completa:

Opciones de inversión:

  • $9.90 USD (pago único)

👉 Conocer la Guía JavaScript

💡 Material actualizado con las mejores prácticas del mercado

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios