¡Domina TypeScript como un pro! Esta guía te lleva de lo básico a lo avanzado con ejemplos claros, respuestas detalladas y explicaciones paso a paso.
No solo memorizarás, entenderás cómo funciona TypeScript para brillar en exámenes y entrevistas técnicas.
TypeScript es un superconjunto de JavaScript que añade tipado estático opcional. Su principal ventaja es que permite detectar errores de tipo en tiempo de compilación, lo que mejora la robustez y el mantenimiento del código en proyectos grandes.
function sumar(a, b) {
return a + b;
}
console.log(sumar(5, "10"));
// Salida: "510" (No hay error, pero el resultado es inesperado) En JavaScript, el motor de ejecución convierte el number 5 y el string “10”, concatenándolos en lugar de sumarlos, lo que genera un resultado no deseado sin lanzar un error.
function sumar(a: number, b: number): number {
return a + b;
}
sumar(5, "10"); // Error de compilación: Argumento de tipo 'string' no es asignable a parámetro de tipo 'number'. TypeScript detecta la incompatibilidad de tipos antes de la ejecución, previniendo el error y asegurando la lógica de la función.
Un tipo define el conjunto de valores que una variable puede tener. Se declaran usando la sintaxis let variable: Tipo = valor;.
string):let nombre: string = "Juan"; Aquí se declara que la variable nombre solo puede almacenar valores de tipo cadena de texto (string).
boolean):let esActivo: boolean = true; Esta variable esActivo está tipada para aceptar únicamente valores booleanos (true o false).
any y unknown. ¿Cuándo usarías cada uno?any desactiva la comprobación de tipos por completo, permitiendo cualquier operación. unknown es un tipo seguro que obliga a realizar una comprobación de tipo (type narrowing) antes de poder operar con la variable. Se prefiere unknown por seguridad.
any):let valorAny: any = "hola";
valorAny.toUpperCase(); // Válido. No hay comprobación de tipo.
valorAny = 10;
valorAny.toFixed(2); // Válido. El tipo any permite realizar cualquier acción sobre la variable sin que el compilador se queje, lo que puede llevar a errores en tiempo de ejecución.
unknown):let valorUnknown: unknown = "hola";
// valorUnknown.toUpperCase(); // Error: 'valorUnknown' es de tipo 'unknown'.
if (typeof valorUnknown === 'string') {
valorUnknown.toUpperCase(); // Válido tras la comprobación.
} Con unknown, el compilador fuerza una verificación explícita (typeof) antes de permitir el uso de métodos específicos del tipo.
interface) en TypeScript y para qué se utiliza?:Una interfaz define la estructura de un objeto, especificando las propiedades y sus tipos. Se utiliza para asegurar que los objetos cumplan con una determinada forma, lo que es útil para la validación y la colaboración en equipo.
interface Coche {
marca: string;
modelo: string;
anio: number;
} Se define una plantilla para los objetos de tipo Coche, especificando que deben tener las propiedades marca (string), modelo (string) y anio (number).
const miCoche: Coche = {
marca: "Toyota",
modelo: "Corolla",
anio: 2020,
};
// const cocheInvalido: Coche = { marca: "Ford" }; // Error: faltan propiedades 'modelo' y 'anio'. Al intentar crear un objeto que no cumple con la estructura de la interfaz, el compilador muestra un error, garantizando la consistencia del código.
type y una interface?:Las interfaces solo se usan para describir la forma de los objetos y se pueden fusionar (declaración de interfaces con el mismo nombre). Los types son más flexibles, pueden definir tipos primitivos, uniones, intersecciones, y no se pueden fusionar.
type para una unión de tipos):type Id = string | number;
let userId: Id = 123;
let productId: Id = "abc-456"; Un type alias permite crear un nuevo nombre para un tipo existente, como una unión de tipos, lo cual no es posible con una interface.
interface):interface Persona {
nombre: string;
}
interface Persona {
edad: number;
}
// La interfaz final 'Persona' tiene 'nombre' y 'edad'.
let p: Persona = { nombre: "Ana", edad: 30 }; Las declaraciones de interface con el mismo nombre se combinan automáticamente, un comportamiento útil para modular el código.
Type Inference) en TypeScript?:Es la capacidad del compilador de TypeScript para deducir automáticamente el tipo de una variable basándose en su valor inicial, sin que sea necesario declararlo explícitamente.
string):let nombre = "Juan"; // TypeScript infiere que 'nombre' es de tipo 'string'.
// nombre = 123; // Error: Tipo 'number' no es asignable a tipo 'string'. El compilador ve que la variable se inicializa con una cadena de texto y automáticamente le asigna el tipo string.
function multiplicar(a: number, b: number) {
return a * b; // TypeScript infiere que el tipo de retorno es 'number'.
} El tipo de retorno de la función se infiere a partir de la operación (a * b), que resulta en un number.
Se usa la sintaxis: function nombre(parametro: TipoParametro): TipoRetorno { ... }
string):function saludar(nombre: string): string {
return `Hola, ${nombre}!`;
} Se especifica que el parámetro nombre debe ser un string y la función debe retornar un valor del mismo tipo.
number):function duplicar(num: number): number {
return num * 2;
} Esta función acepta un número como entrada y garantiza que el valor de retorno también sea un número.
Union Types):Permite que una variable pueda tener uno de varios tipos posibles. Se define con el operador |, por ejemplo: let id: string | number;.
string o number):let id: string | number;
id = 101; // Válido
id = "abc-123"; // Válido
// id = true; // Error: Tipo 'boolean' no es asignable a tipo 'string | number'. La variable id puede almacenar tanto un número como una cadena de texto, pero no otro tipo de dato.
function imprimirId(id: string | number) {
console.log(`El ID es: ${id}`);
}
imprimirId(5);
imprimirId("xyz"); La función imprimirId está diseñada para trabajar con datos que pueden ser de dos tipos diferentes, lo que la hace más flexible.
Tuple) y cómo se diferencia de un array normal?:Una tupla es un array con un número fijo y predefinido de elementos, donde cada elemento puede tener un tipo diferente. A diferencia de los arrays, la tupla mantiene un orden y una longitud estricta. Ejemplo: [string, number].
let par: [string, number] = ["edad", 25];
// par = [25, "edad"]; // Error: Tipos en orden incorrecto. La tupla asegura que el primer elemento sea un string y el segundo un number, y no permite reordenarlos.
let coordenadas: [number, number] = [40.7128, -74.0060];
// coordenadas.push("Error"); // Error: No se puede agregar un string a la tupla. La tupla garantiza no solo los tipos, sino también la cantidad de elementos, lo que la hace ideal para datos estructurados como coordenadas.
Se usa el operador ? después del nombre de la propiedad. Esto indica que la propiedad puede estar presente o no en el objeto.
interface Usuario {
nombre: string;
edad?: number; // Propiedad opcional
email?: string; // Propiedad opcional
} Las propiedades edad y email son opcionales, por lo que un objeto Usuario puede existir sin ellas.
const usuario1: Usuario = { nombre: "Ana" }; // Válido
const usuario2: Usuario = { nombre: "Luis", edad: 30 }; // Válido
const usuario3: Usuario = { nombre: "María", edad: 25, email: "maria@example.com" }; // Válido Todos los objetos son válidos porque solo nombre es obligatorio, mientras que edad y email son opcionales.
any y cuándo debería evitarse?:El tipo any desactiva completamente el sistema de tipos de TypeScript, permitiendo cualquier valor. Debe evitarse porque elimina las ventajas de TypeScript como la detección de errores y el autocompletado.
any):let dato: any = "Hola";
dato = 42;
dato = true;
dato.metodoInexistente(); // No hay error en tiempo de compilación Con any, TypeScript no puede detectar errores como llamar a métodos inexistentes, perdiendo la seguridad de tipos.
unknown):let dato: unknown = "Hola";
if (typeof dato === "string") {
console.log(dato.toUpperCase()); // Seguro después de la verificación
} unknown es más seguro que any porque requiere verificaciones de tipo antes de usar el valor.
interface y type en TypeScript?:Ambos definen formas de objetos, pero interface es extensible (se puede ampliar) y type es más flexible (puede definir uniones, primitivos, etc.). interface es mejor para objetos que pueden crecer.
interface):interface Animal {
nombre: string;
}
interface Animal { // Se puede extender
edad: number;
}
const perro: Animal = { nombre: "Rex", edad: 3 }; // Ambas propiedades requeridas Las interfaces se pueden extender declarándolas múltiples veces, combinando automáticamente sus propiedades.
type):type ID = string | number; // Unión de tipos
type Usuario = {
id: ID;
nombre: string;
};
// type Usuario = { email: string }; // Error: No se puede redeclarar type puede definir uniones, primitivos y alias complejos, pero no se puede extender como interface.
Es el archivo de configuración para el compilador de TypeScript (tsc). Contiene opciones como el destino de compilación (ES5, ES6), la ruta de los archivos de entrada, y si se deben habilitar ciertas comprobaciones de tipo.
target y outDir):{
"compilerOptions": {
"target": "es5",
"outDir": "./dist"
}
} Esta configuración le indica al compilador que genere código JavaScript compatible con la versión ES5 y lo guarde en una carpeta específica.
strict):{
"compilerOptions": {
"strict": true
}
} La opción strict activa un conjunto de reglas de tipado más rigurosas para un código más seguro y robusto.
as keyword)? ¿Cuándo se usa?:Es una forma de “indicarle” al compilador que sabes más sobre el tipo de una variable de lo que él puede inferir. Se utiliza para anular la inferencia de tipos por defecto, pero debe usarse con cuidado.
const miCanvas = document.getElementById("main-canvas") as HTMLCanvasElement; getElementById puede devolver HTMLElement o null. Con as HTMLCanvasElement, le aseguramos al compilador que el elemento es del tipo que necesitamos.
any):let algo: any = "esto es un string";
let longitud: number = (algo as string).length; // Indicamos al compilador que 'algo' es un string. Se utiliza para tratar una variable de tipo any como un tipo específico para poder acceder a sus propiedades y métodos.
Type Guards)?:Son técnicas para verificar el tipo de una variable en tiempo de ejecución, permitiendo que TypeScript infiera automáticamente el tipo correcto dentro de un bloque de código específico.
typeof):function procesar(valor: string | number) {
if (typeof valor === "string") {
console.log(valor.toUpperCase()); // TypeScript sabe que es string
} else {
console.log(valor.toFixed(2)); // TypeScript sabe que es number
}
} El typeof actúa como type guard, permitiendo que TypeScript infiera automáticamente el tipo correcto en cada rama del if.
interface Perro { ladrar(): void; }
interface Gato { maullar(): void; }
function esPerro(animal: Perro | Gato): animal is Perro {
return (animal as Perro).ladrar !== undefined;
}
function hacerSonido(animal: Perro | Gato) {
if (esPerro(animal)) {
animal.ladrar(); // TypeScript sabe que es Perro
} else {
animal.maullar(); // TypeScript sabe que es Gato
}
} La función esPerro es un type guard personalizado que usa la sintaxis animal is Perro para indicar a TypeScript el tipo específico.
typeof):Son expresiones que verifican si una variable es de un tipo determinado en un bloque de código. Por ejemplo, typeof y instanceof son guardianes de tipo integrados.
function imprimirLongitud(texto: string | string[]) {
if (typeof texto === "string") {
console.log(texto.length); // Aquí 'texto' es un string.
} else {
console.log(texto.join(" ").length); // Aquí 'texto' es un string[].
}
} La condición typeof texto === "string" funciona como un “guardián”, lo que permite al compilador saber el tipo de texto dentro de ese bloque de código.
instanceof):class Perro {}
class Gato {}
function esAnimal(animal: Perro | Gato) {
if (animal instanceof Perro) {
console.log("Es un perro.");
} else {
console.log("Es un gato.");
}
} Se utiliza instanceof para verificar si un objeto es una instancia de una clase específica, lo que ayuda a refinar su tipo.
instanceof):class Perro {}
class Gato {}
function esAnimal(animal: Perro | Gato) {
if (animal instanceof Perro) {
console.log("Es un perro.");
} else {
console.log("Es un gato.");
}
} Se utiliza instanceof para verificar si un objeto es una instancia de una clase específica, lo que ayuda a refinar su tipo.
Readonly properties) y cómo se definen?:Son propiedades de un objeto que solo se pueden asignar en el momento de la inicialización y no pueden ser modificadas posteriormente. Se definen con la palabra clave readonly.
readonly en una interfaz):interface Punto {
readonly x: number;
readonly y: number;
}
let p1: Punto = { x: 10, y: 20 };
// p1.x = 5; // Error: No se puede asignar a 'x' porque es una propiedad de solo lectura. Una vez que se inicializa el objeto, las propiedades marcadas como readonly no se pueden reasignar, garantizando la inmutabilidad.
readonly en una clase):class Usuario {
readonly id: string;
constructor(id: string) {
this.id = id;
}
}
const usuario = new Usuario("user-123");
// usuario.id = "user-456"; // Error: No se puede modificar la propiedad 'id'. Esto protege la propiedad id de ser cambiada accidentalmente después de que la instancia ha sido creada.
Es la capacidad de extraer valores de objetos o arrays y asignarlos a variables con tipado específico, combinando la sintaxis de desestructuración de JavaScript con las características de tipado de TypeScript.
interface Persona {
nombre: string;
edad: number;
}
const persona: Persona = { nombre: "Juan", edad: 30 };
const { nombre, edad } = persona; // TypeScript infiere los tipos de 'nombre' y 'edad'. Los tipos de las variables nombre y edad se infieren automáticamente de la interfaz Persona.
let numeros: [number, number] = [1, 2];
let [x, y] = numeros; // x: number, y: number. En este caso, TypeScript sabe que x y y son números basados en la tupla numeros.
Generics) en TypeScript?:Los genéricos permiten escribir componentes que funcionan con múltiples tipos de datos, en lugar de uno solo. Esto proporciona flexibilidad y reutilización sin perder los beneficios de la verificación de tipos.
function obtenerPrimerElemento<T>(arr: T[]): T {
return arr[0];
}
const primerNumero = obtenerPrimerElemento([1, 2, 3]); // Tipo 'number'.
const primeraLetra = obtenerPrimerElemento(["a", "b", "c"]); // Tipo 'string'. La función obtenerPrimerElemento puede trabajar con arrays de cualquier tipo, y el tipo de retorno se infiere a partir del tipo del array pasado como argumento.
interface Caja<T> {
contenido: T;
}
const cajaNumero: Caja<number> = { contenido: 100 };
const cajaTexto: Caja<string> = { contenido: "Hola" }; La interfaz Caja se puede reutilizar para crear contenedores de cualquier tipo de dato, manteniendo la seguridad de tipos.
Los módulos son una forma de organizar el código en archivos separados para encapsular funcionalidades. Se utilizan las palabras clave export e import. Los namespaces son una característica más antigua para organizar código globalmente y se consideran obsoletos.
// archivo 'util.ts'
export function sumar(a: number, b: number): number {
return a + b;
}
// archivo 'main.ts'
import { sumar } from "./util";
console.log(sumar(2, 3)); La función sumar se exporta desde un archivo para ser utilizada en otro archivo a través de la instrucción import.
namespace):namespace Matematicas {
export function sumar(a: number, b: number) {
return a + b;
}
}
// Matematicas.sumar(2, 3); Un namespace agrupa el código para evitar colisiones de nombres globales, pero el enfoque modular con import/export es el estándar moderno.
Decorators) en TypeScript?:Los decoradores son una característica experimental que permite añadir metadatos o modificar clases, propiedades, métodos o parámetros en tiempo de diseño. Se utilizan con el símbolo @.
function HolaMundo(target: Function) {
console.log("Hola desde el decorador!");
}
@HolaMundo
class MiClase {} El decorador @HolaMundo se ejecuta cuando se declara la clase MiClase, lo que puede ser útil para la inyección de dependencias o la instrumentación de código.
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Llamando al método ${key} con argumentos:`, args);
return originalMethod.apply(this, args);
};
return descriptor;
}
class Servicio {
@log
imprimir(mensaje: string) {
console.log(mensaje);
}
}
new Servicio().imprimir("Hola!"); El decorador @log modifica el método imprimir para que imprima un mensaje antes de su ejecución original, lo que es útil para el registro (logging) o la validación.
null y undefined en TypeScript y cómo se relacionan con la opción de compilador strictNullChecks?:undefined significa que una variable ha sido declarada pero no se le ha asignado un valor. null es un valor asignado intencionalmente para representar la ausencia de un objeto. La opción strictNullChecks fuerza una verificación más estricta.
strictNullChecks: false):// Con "strictNullChecks": false
let miString: string = "hola";
miString = null; // Válido. Sin la opción strictNullChecks, null y undefined pueden ser asignados a cualquier tipo de variable, lo que puede provocar errores de null-pointer en tiempo de ejecución.
strictNullChecks: true):// Con "strictNullChecks": true
let miString: string = "hola";
// miString = null; // Error: El tipo 'null' no es asignable al tipo 'string'.
let miStringOpcional: string | null = "hola"; // Válido, se usa una unión de tipos.
miStringOpcional = null; Con strictNullChecks activado, el compilador exige que se use una unión de tipos (string | null) para poder asignar null a una variable, lo que promueve un código más seguro.