Tipos en TypeScript

Repaso de tipos básicos y complejos en TypeScript. Qué son los genéricos y la conversión de tipos.

Tipos en TypeScript

Tipos primitivos en TypeScript

En TypeScript, al igual que en JavaScript, existen tipos primitivos que representan datos simples y básicos. Veamos los tipos primitivos principales en TypeScript con ejemplos de código:

Números

Representa valores numéricos, ya sean enteros o decimales.

let edad: number = 25;
let precio: number = 99.99;

// Ejemplo de operaciones con números
let suma: number = edad + precio;

Cadenas

Son valores de texto o cadenas de caracteres (strings en inglés).

let nombre: string = 'Juan';
let mensaje: string = `Hola, ${nombre}!`;

// Ejemplo de longitud de cadena
let longitud: number = mensaje.length;

Booleanos

Representa valores lógicos, verdadero true o falso false.

let activo: boolean = true;
let esMayorDeEdad: boolean = edad >= 18;

// Ejemplo de expresiones lógicas
let puedeVotar: boolean = esMayorDeEdad && activo;

null y undefined

Son tipos que tienen valores null (un valor asignado) y undefined (variable declarada pero si definir ningún valor).

let valorNulo: null = null;
let valorNoDefinido: undefined = undefined;

Symbol

Representa valores únicos e inmutables utilizados como identificadores de propiedad en objetos. Introducidos en ECMAScript 2015 (ES6).

const simbolo1: symbol = Symbol('id');
const simbolo2 = Symbol('id'); // Los símbolos son siempre únicos

// Ejemplo de uso de símbolos en objetos
const persona = {
  nombre: 'Ana',
  [simbolo1]: 28 // Uso de un símbolo como clave de propiedad
};

BigInt

Representa valores enteros mayores que 2^53 - 1 o menores que -(2^53 - 1).

const valorGrande: bigint = 9007199254740991n;
const otroValor: bigint = BigInt(100); // Creación de un valor BigInt

// Ejemplo de operaciones con bigints
const sumaBigInt: bigint = valorGrande + otroValor;

Estos son algunos de los tipos primitivos básicos en TypeScript que se utilizan para definir variables y estructuras de datos simples en el código.

# Tipos Complejos en TypeScript

Los tipos complejos en TypeScript permiten representar y trabajar con estructuras de datos más sofisticadas y flexibles, lo que proporciona un mayor nivel de seguridad y claridad en el desarrollo de aplicaciones. Aquí te muestro algunos de los tipos complejos principales con ejemplos de código:

Arrays

Representa una colección ordenada de elementos del mismo tipo.

let numeros: number[] = [1, 2, 3, 4, 5];
let palabras: string[] = ['Hola', 'Mundo'];

// Uso de arrays con tipos múltiples (unión de tipos)
let variedad: (number | string)[] = [10, 'veinte', 30];

Tuple

Una tupla es un array de longitud fija que permite definir tipos específicos para cada posición.

let tupla: [string, number, boolean] = ['Hola', 10, true];

// Acceso a elementos de la tupla
let mensaje: string = tupla[0];
let valor: number = tupla[1];
let activo: boolean = tupla[2];

Object

Representa un objeto genérico con propiedades y valores.

let persona: { nombre: string; edad: number } = {
  nombre: 'Ana',
  edad: 30
};

// Acceso a las propiedades del objeto
console.log(persona.nombre);
console.log(persona.edad);

Any

Representa un tipo dinámico que puede tomar cualquier valor.

let variableDinamica: any = 10;
variableDinamica = 'Hola';
variableDinamica = true;

Unión de tipos

Permite definir variables que pueden contener más de un tipo.

let resultado: number | string;
resultado = 100;
resultado = 'Éxito';

Enum

Permite definir un conjunto de constantes nombradas.

enum DiaSemana {
  Lunes,
  Martes,
  Miércoles,
  Jueves,
  Viernes
}

let diaLaboral: DiaSemana = DiaSemana.Lunes;
console.log(diaLaboral); // Imprimirá: 0 (valor asignado a Lunes)

Type y interface

En TypeScript, tanto type como interface son herramientas que permiten definir estructuras de datos personalizadas. Aunque pueden parecer similares en algunos aspectos, tienen diferencias en su funcionamiento y uso. Veamos cada uno de ellos:

Type

type es una forma de definir un alias para un tipo existente o crear un nuevo tipo a partir de una combinación de otros tipos. Esto permite reutilizar tipos existentes o crear nuevos tipos basados en composiciones, uniones, intersecciones u otras operaciones con tipos.

Ejemplo:

type Coordenada = {
  x: number;
  y: number;
};

type Punto = Coordenada & { color: string };

let punto: Punto = { x: 10, y: 20, color: 'rojo' };

En el ejemplo anterior, Punto es un tipo que combina las propiedades de Coordenada y agrega una propiedad adicional color. Los tipos pueden ser usados para definir tipos más complejos y reutilizables.

Interface

interface, por otro lado, se utiliza principalmente para definir la forma de una estructura de datos. Es una forma de declarar contratos que los objetos deben cumplir. Las interfaces son más adecuadas para definir la forma de un objeto y permiten la herencia entre interfaces para extender su comportamiento.

Ejemplo:

interface Coordenada {
  x: number;
  y: number;
}

interface Punto extends Coordenada {
  color: string;
}

let punto: Punto = { x: 10, y: 20, color: 'rojo' };

En este ejemplo, Punto es una interfaz que extiende la interfaz Coordenada, agregando la propiedad color. Las interfaces también pueden ser implementadas por clases para garantizar que la clase cumpla con la estructura definida por la interfaz.

Diferencias entre ambas

  • Extensibilidad: Las interfaces pueden ser extendidas por otras interfaces, lo que permite crear jerarquías de interfaces. Los tipos type no admiten herencia directa, aunque se pueden crear tipos compuestos y utilizarlos de manera similar.

  • Declaración vs. composición: Las interfaces son principalmente para la declaración de formas de objetos, mientras que los tipos type son más flexibles y pueden ser usados para definir tipos compuestos, uniones, intersecciones y otros tipos más complejos.

En la práctica, la elección entre type e interface depende del escenario particular y las necesidades del desarrollo. A menudo, ambas pueden ser utilizadas en conjunto para beneficiarse de las diferentes características que ofrecen. Es importante entender sus diferencias y utilizarlas de manera efectiva.

Genéricos en TypeScript

Los genéricos (generics) es un término en TypeScript que sirve para describir la característica que permite escribir código flexible y reutilizable al trabajar con tipos de datos. Permiten la creación de componentes que pueden funcionar con una variedad de tipos sin perder la información de tipo durante la ejecución.

Ejemplos con genéricos

Los genéricos se definen utilizando el símbolo <T>, donde T es una variable de tipo. Estos tipos pueden ser utilizados dentro de funciones, clases y otros constructores para proporcionar flexibilidad en la elección de tipos. Por ejemplo, la función identidad soporta varios tipos:

function identidad<T>(valor: T): T {
  return valor;
}

let numero: number = identidad(5);       // T se infiere como number
let palabra: string = identidad('Hola'); // T se infiere como string

Puedes utilizar varias variables de tipo genérico en una función para trabajar con múltiples tipos.

function imprimirPar<T, U>(a: T, b: U): void {
  console.log(`${a}, ${b}`);
}

imprimirPar(1, 'Dos');  // Salida: 1, Dos
imprimirPar('Tres', 4); // Salida: Tres, 4

Uso en Clases

Clases Genéricas

Las clases también pueden hacer uso de generics para proporcionar flexibilidad en la elección de tipos.

class Contenedor<T> {
  valor: T;

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

let contenedorNumero = new Contenedor<number>(5);
let contenedorString = new Contenedor<string>('Hola');

Uso de Múltiples Tipos Genéricos

Una clase puede tener múltiples variables de tipo genérico.

class ParOrdenado<T, U> {
  constructor(public primero: T, public segundo: U) {}
}

let par = new ParOrdenado<number, string>(1, 'Dos');
console.log(par.primero);  // Salida: 1
console.log(par.segundo); // Salida: Dos

Type assertions

¿Qué son las Type assertions?

Las Type assertions o conversión de tipos, también conocidas como "casting", son una característica presente en muchos lenguajes de programación como Java o C#. También está presente en TypeScript. Básicamente sirve para ayudar al compilador a determinar un tipo específico a un valor cuando este no puede determinarlo por si mismo.

Sintaxis y uso

La sintaxis básica de una Type Assertion es utilizar el operador as o la notación <>. Aquí hay ejemplos de ambas formas:

// Sintaxis con <>
let longitud: number = (<string>miVariable).length;

// Sintaxis con 'as'
let longitud: number = (miVariable as string).length;

El uso de Type assertions se vuelve útil en situaciones como:

let miVariable: any = 'Hola, Mundo!';
let longitud: number = (miVariable as string).length;

En este caso, miVariable se declara como tipo any, pero el desarrollador sabe que, en este contexto específico, su valor será una cadena de texto (string). La Type Assertion le permite a TypeScript tratar miVariable como una cadena, permitiendo el acceso a la propiedad length.

Mejores Prácticas

Aunque las Type assertions pueden ser útiles en ciertos escenarios, es importante usarlas con precaución para evitar situaciones de ejecución impredecibles. Veamos las mejores prácticas:

  1. Utilízalas sólo cuando sea necesario

    • Las Type assertions deben usarse solo cuando sea necesario y no como una solución predeterminada.
  2. Evita Type assertions con any

    • Trata de evitar Type assertions en variables declaradas como any, ya que esto anula gran parte de la seguridad de tipo proporcionada por TypeScript.
  3. Usa Type Guards cuando sea posible

    • En lugar de usar Type Assertions, considera el uso de Type Guards (funciones que realizan comprobaciones de tipo) para proporcionar evidencia al compilador sobre el tipo de una variable.
// Ejemplo de Type Guard
function esCadena(valor: any): valor is string {
  return typeof valor === 'string';
}

let miVariable: any = 'Hola, Mundo!';
if (esCadena(miVariable)) {
  let longitud: number = miVariable.length; // TypeScript ahora entiende que es una cadena
}

Las Type assertions pueden ser valiosas en ciertos escenarios, pero es crucial utilizarlas con responsabilidad y considerar alternativas más seguras cuando sea posible. Esto ayuda a mantener la integridad del sistema de tipos de TypeScript y a prevenir errores difíciles de depurar.

Conclusión

En este artículo hemos visto los tipos básicos de TypeScript, los tipos complejos, qué son los genéricos y la conversión de tipos. Comprender todos estos términos no solo nos ayudará a mejorar la calidad del código, sino que también nos abre la puerta a prácticas de desarrollo más avanzadas y eficientes para el desarrollo de nuestras aplicaciones con TypeScript.

Si quieres profundizar más sobre tipos puedes visitar la documentacion oficial.