TypeScript: Fundamentos de Sintaxis y Características

TypeScript es un superconjunto de JavaScript que sigue la especificación ECMAScript 6 (ES6) y es compatible con ECMAScript 5 (ES5).

Para experimentar con TypeScript, puedes usar el compilador en línea en TypeScript Playground.

Para instalar el compilador localmente, usa npm:

npm install -g typescript
tsc -version

Para compilar un archivo TypeScript (.ts) a JavaScript (.js), utiliza el comando:

tsc nombre_archivo.ts

Cadenas de Texto

Cadenas Multilínea

Las cadenas multilínea se definen utilizando acentos graves (``) y permiten saltos de línea directos dentro de ellas.

let mensajeMultilinea = `<div>
            <span>Hola Mundo</span>
        </div>`;

Plantillas de Cadenas

Dentro de las cadenas multilínea, puedes incrustar expresiones usando la sintaxis ${expresion}. Esta característica está disponible únicamente en cadenas multilínea.

let nombre = "Juan Pérez";
let edad = 26;
let htmlPlantilla = `
    <div>
        <span>Mi nombre es ${nombre}</span>
        <span>Mi edad es ${edad}</span>
    </div>
`;

Descomposición Automática de Cadenas

Cuando una cadena multilínea se utiliza para llamar a una función, las expresiones incrustadas se pasan como argumentos a dicha función. La cadena se descompone en un array de partes literales.

function procesarPlantilla(partes: TemplateStringsArray, ...valores: any[]) {
    console.log("Partes:", partes);
    console.log("Valores:", valores);
}

let nombreUsuario = "Ana Gómez";
let edadUsuario = 22;
procesarPlantilla`Mi nombre es ${nombreUsuario}, mi edad es ${edadUsuario} y tengo más datos`;

// Salida esperada para 'partes': ["Mi nombre es ", ", mi edad es ", " y tengo más datos"]
// Salida esperada para 'valores': ["Ana Gómez", 22]

Variables

Especificación de Tipos de Variables

Al declarar variables, puedes especificar su tipo (por ejemplo, number, string, boolean, Array). Usa any si no deseas restringir el tipo.

TypeScript detectraá errores si la asignación no coincide con el tipo declarado. Sin embargo, estos errores son de compilación de TypeScript; el JavaScript resultante se ejecutará sin problemas.

let nombre: string = "Carlos";
let esMayorDeEdad: boolean = true;
let contador: number = 123;
let datoDinamico: any = 666;
let listaNumeros: Array<number> = [1, 2, 3];
// Sintaxis alternativa para arrays:
let otraListaNumeros: number[] = [1, 2, 3];</number>

Tipos en Funciones

Puedes especificar los tipos de los parámetros y el valor de retorno de una función.

function saludar(nombre: string): string {
    return `Hola, ${nombre}`;
}

Para funciones que no retornan ningún valor, usa void.

function mostrarMensaje(mensaje: string): void {
    console.log(mensaje);
}

Tipos Personalizados

Puedes usar clases como tipos para tus variables.

class Persona {
    nombre: string;
    edad: number;
}
let personaEjemplo: Persona = new Persona();

Inferencia de Tipos

Si no se especifica un tipo y no se usa any, TypeScript inferirá el tipo de la variable basándose en el valor inicial asignado.

let ciudad = "Madrid"; // TypeScript infiere que 'ciudad' es de tipo string.

Valores Predeterminados para Variables

Los valores predeterminados se pueden asignar directamente a las variables.

let nombrePredeterminado: string = "Invitado";

Valores Predeterminados en Parámetros de Funciones

En TypeScript, si una función tiene parámetros definidos y se llama con menos argumentos, generará un error (a diferencia de JavaScript, donde los parámetros faltantes son undefined). Los valores predeterminados permiten omitir argumentos opcionales.

Generalmente, los parámetros con valores predeterminados se colocan al final de la lista de parámetros.

function configurarOpciones(opcion1: string, opcion2: string, opcion3: string = 'valorDefecto') {
    console.log(opcion1);
    console.log(opcion2);
    console.log(opcion3);
}

configurarOpciones("a", "b", "c"); // Salida: a, b, c
configurarOpciones("a", "b");    // Salida: a, b, valorDefecto

Parámetros Opcionales en Funciones

Se marca un parámetro como opcional añadiendo un signo de interrogación (?) después de su nombre.

function procesarDatos(datoObligatorio: string, datoOpcional?: number) {
    console.log(datoObligatorio);
    console.log(datoOpcional);
}

Los parámetros opcionales y los parámetros con valores predeterminados deben ir después de los parámetros obligatorios.

Nuevas Características de Funciones

Rest y Spread Operators (...)

El operador de tres puntos (...) se utiliza para manejar un número variable de argumentos.

1. En la declaración de funciones

Como parámetro, representa un array con un número indeterminado de argumentos.

function sumarTodos(...numeros: number[]): number {
    return numeros.reduce((total, num) => total + num, 0);
}

console.log(sumarTodos(1, 2, 3));      // Salida: 6
console.log(sumarTodos(8, 9, 10, 11, 12)); // Salida: 50

2. En la invocación de funciones

Al llamar a una función, se utiliza para expandir los elementos de un array como argumentos individuales.

function multiplicar(a: number, b: number, c: number) {
    console.log(a * b * c);
}

let factores1 = [2, 3];
let factores2 = [5, 6, 7];

multiplicar(...factores1, 4); // Equivalente a multiplicar(2, 3, 4) -> Salida: 24
multiplicar(...factores2);    // Equivalente a multiplicar(5, 6, 7) -> Salida: 210

Nota: El uso de Spread en la invocación de funciones es una característica de ES6. TypeScript permite su uso y lo compila correctamente.

Funciones Generadoras (Generators)

Las funciones generadoras permiten controlar la ejecución de una función, pausándola y reanudándola manualmente, similar a un punto de interrupción.

  1. Se declaran con un asterisco después de function: function* miGenerador() {}.
  2. Se usa la palabra clave yield para pausar la ejecución y opcionalmente retornar un valor.
  3. La llamada a una función generadora no ejecuta su código de inmediato, sino que devuelve un objeto iterador.
  4. Se llama al método next() del iterador para ejecutar la función hasta el sigueinte yield.
  5. El valor retornado por yield se accede a través de la propiedad value del objeto devuelto por next().
function* secuenciaNumeros() {
    console.log("Inicio de la secuencia");
    yield 10; // Pausa 1
    console.log("Continuando...");
    yield 20; // Pausa 2
    console.log("Fin de la secuencia");
}

const iterador = secuenciaNumeros();

console.log(iterador.next()); // Ejecuta hasta el primer yield, imprime "Inicio de la secuencia", retorna { value: 10, done: false }
console.log(iterador.next()); // Ejecuta hasta el segundo yield, imprime "Continuando...", retorna { value: 20, done: false }
console.log(iterador.next()); // Ejecuta el resto, imprime "Fin de la secuencia", retorna { value: undefined, done: true }

Nota: Las funciones generadoras son una característica de ES6. TypeScript las soporta para la compilación.

Expresiones de Flecha y Bucles

Funciones de Flecha (Arrow Functions)

Las funciones de flecha usan la sintaxis => en lugar de la palabra clave function.

// Función tradicional
function saludarTradicional() {
    console.log("Hola");
}

// Función de flecha
const saludarFlecha = () => {
    console.log("Hola");
};

Usos Comunes

Son especialmente útiles para funciones anónimas, como callbacks en setTimeout, filter, setInterval, etc.

setTimeout((a, b) => {
    console.log(a + b);
}, 1000);

Sintaxis Abreviada

Para funciones de una sola línea, se puede omitir la palabra clave return y las llaves.

// Equivalente a:
// setTimeout(function(a, b){ return a + b; }, 1000);
setTimeout((a, b) => a + b, 1000);

// Si hay un solo parámetro, se pueden omitir los paréntesis:
setTimeout(arg => console.log(arg), 1000);

El this en Funciones de Flecha

Las funciones de flecha resuelven el problema del this en JavaScript. A diferencia de las funciones tradicionales, donde this puede cambiar de contexto (a menudo apuntando a window en callbacks asíncronos), las funciones de flecha heredan el this de su entorno léxico (el this del scope donde fueron definidas).

class PersonaTradicional {
    constructor() {
        this.nombre = "Carlos";
        setTimeout(function() {
            // 'this' aquí se refiere a window (o undefined en modo estricto)
            console.log(this.nombre); // undefined
        }, 1000);
    }
}

class PersonaFlecha {
    constructor() {
        this.nombre = "Ana";
        setTimeout(() => {
            // 'this' aquí se refiere a la instancia de PersonaFlecha
            console.log(this.nombre); // "Ana"
        }, 1000);
    }
}

// new PersonaTradicional();
// new PersonaFlecha();

Bucle for...of

El bucle for...of (característica de ES6 soportada por TypeScript) permite iterar sobre elementos de estructuras iterables como arrays, strings y objetos Map/Set. A diferencia de forEach, soporta break y continue.

const numeros = [1, 2, 3, 4, 5, 6];
for (const numero of numeros) {
    if (numero >= 2 && numero <= 3) {
        continue; // Salta a la siguiente iteración
    }
    console.log(numero); // Salida: 1, 4, 5, 6
}

Características de Programación Orientada a Objetos

Clases

Declaración

Las clases se declaran con la palabra clave class. La instanciación se realiza con new. Las propiedades y métodos se declaran directamente dentro de la clase.

Al compilar, los métodos de clase se colocan en el prototipo, mientras que las propiedades se inicializan dentro del constructor.

class Vehiculo {
    velocidadActual: number;
    readonly marca: string; // Propiedad de solo lectura

    constructor(marcaVehiculo: string) {
        this.marca = marcaVehiculo;
        this.velocidadActual = 0;
        console.log(`Vehículo ${this.marca} creado.`);
    }

    acelerar(velocidad: number): void {
        this.velocidadActual += velocidad;
        console.log(`Acelerando. Velocidad actual: ${this.velocidadActual} km/h`);
    }
}

const miCoche = new Vehiculo("Toyota");
miCoche.acelerar(50);

Modificadores de Acceso

TypeScript soporta public, private y protected.

  • public (por defecto): Accesible desde cualquier lugar.
  • private: Accesible solo dentro de la clase donde se declara.
  • protected: Accesible dentro de la clase y por sus clases derivadas (subclases).

Constructor

El código dentro del constructor se ejecuta una sola vez al instanciar la clase. Es un punto de entrada para pasar parámetros al crear un objeto.

Los parámetros del constructor no se convierten automáticamente en propiedades de la clase, a menos que se les aplique un modificador de acceso.

class Producto {
    // Propiedad declarada explícitamente
    nombre: string;

    // Parámetro del constructor con modificador de acceso
    // Crea automáticamente la propiedad 'id' y la inicializa
    constructor(nombreProducto: string, public id: number) {
        this.nombre = nombreProducto;
        console.log(`Producto ${this.nombre} con ID ${this.id} creado.`);
    }
}

const libro = new Producto("El Quijote", 101);
// console.log(libro.id); // Acceso a la propiedad generada por el modificador
// console.log(libro.nombre); // Acceso a la propiedad declarada explícitamente

Herencia de Clases

Se utiliza la palabra clave extends para establecer una relación de herencia.

class Animal {
    nombre: string;
    constructor(nombreAnimal: string) {
        this.nombre = nombreAnimal;
    }
    hacerSonido(): void {
        console.log("Sonido genérico");
    }
}

class Perro extends Animal {
    constructor(nombrePerro: string) {
        super(nombrePerro); // Llama al constructor de la clase padre
    }

    hacerSonido(): void {
        console.log("Guau!"); // Sobrescribe el método del padre
    }

    ladrar(): void {
        console.log("Ladrando fuerte...");
    }
}

const miPerro = new Perro("Fido");
miPerro.hacerSonido(); // Salida: Guau!
miPerro.ladrar();     // Salida: Ladrando fuerte...

Si una subclase tiene su propio constructor, debe llamar a super() para ejecutar el constructor de la superclase antes de acceder a this.

Genéricos (Generics)

Los genéricos permiten crear componentes (clases, funciones, tipos) que funcionan con una variedad de tipos, manteniendo la seguridad de tipos.

class Contenedor<T> {
    private valor: T;

    constructor(valorInicial: T) {
        this.valor = valorInicial;
    }

    obtenerValor(): T {
        return this.valor;
    }
}

// Usando el genérico con string
let contenedorString = new Contenedor<string>("Hola Mundo Genérico");
console.log(contenedorString.obtenerValor().toUpperCase()); // Acceso seguro a métodos de string

// Usando el genérico con number
let contenedorNumero = new Contenedor<number>(12345);
console.log(contenedorNumero.obtenerValor().toFixed(2)); // Acceso seguro a métodos de number

Interfaces

Las interfaces definen contratos de estructura para objetos o clases. Obligan a que las implementaciones cumplan con las propiedades y métodos especificados.

1. Definición de una interfaz

interface ConfiguracionUsuario {
    nombre: string;
    edad?: number; // Propiedad opcional
    readonly id: number; // Propiedad de solo lectura
}

2. Uso de interfaces como parámetros de función/constructor

function crearUsuario(config: ConfiguracionUsuario): void {
    console.log(`Usuario creado: ${config.nombre}, ID: ${config.id}`);
    if (config.edad) {
        console.log(`Edad: ${config.edad}`);
    }
}

const miConfiguracion: ConfiguracionUsuario = {
    id: 1,
    nombre: "Elena"
    // edad es opcional
};
crearUsuario(miConfiguracion);

3. Uso de interfaces en clases

interface Volador {
    volar(): void;
}

class Avion implements Volador {
    volar(): void {
        console.log("El avión está despegando.");
    }
}

class Pato implements Volador {
    volar(): void {
        console.log("El pato está volando bajo.");
    }
}

Módulos

Un archivo de TypeScript se considera un módulo. Los módulos permiten encapsular código y exportar explícitamente lo que se desea compartir (clases, variables, funciones).

export se usa para exponer miembros de un módulo.

import se usa para consumir miembros de otros módulos.

// archivo: utilidades.ts
export const PI = 3.14159;

export function calcularCircunferencia(radio: number): number {
    return 2 * PI * radio;
}

// archivo: principal.ts
import { calcularCircunferencia, PI } from './utilidades';

console.log(`El valor de PI es: ${PI}`);
console.log(`Circunferencia de radio 5: ${calcularCircunferencia(5)}`);

Anotaciones (Decorators)

Los decoradores son una característica experimental (similar a las anotaciones en otros lenguajes) que permiten añadir metadatos a clases, métodos, propiedades o parámetros. Son comúnmente utilizados por frameworks como Angular.

Para usarlos, a menudo se necesita habilitar la opción experimentalDecorators en el archivo tsconfig.json.

// Ejemplo conceptual de un decorador de clase
function logearClase(constructor: Function) {
    console.log(`Clase ${constructor.name} ha sido definida.`);
}

@logearClase
class EjemploDecorador {
    constructor() {
        console.log("Instancia de EjemploDecorador creada.");
    }
}

const instancia = new EjemploDecorador(); // Imprimirá "Clase EjemploDecorador ha sido definida." y "Instancia de EjemploDecorador creada."

Frameworks como Angular utilizan decoradores como @Component, @Injectable, @Input, @Output para configurar y estructurar aplicaciones.

Archivos de Definición de Tipos (.d.ts)

Para usar bibliotecas de JavaScript de terceros (como jQuery) en TypeScript y beneficiarse de la verificación de tipos, necesitas sus archivos de definición de tipos. Estos archivos describen la API de la biblioteca en términos de tipos de TypeScript.

Herramientas como npm o typings se utilizan para instalar estos archivos de definición (por ejemplo, npm install --save-dev @types/jquery).

// Ejemplo: Uso de jQuery después de instalar sus tipos (@types/jquery)
import * as $ from 'jquery';

$(document).ready(() => {
    $('button').on('click', () => {
        alert('Botón clickeado!');
    });
});

Etiquetas: TypeScript JavaScript ES6 tipos clases

Publicado el 9-29 05:24