typescript-uniones

Uso de Uniones en TypeScript

Los tipos unión en TypeScript permiten que una variable o una función acepten múltiples tipos de datos. Esto nos da flexibilidad para definir tipos y nos ayuda en situaciones donde los valores pueden ser de más de un tipo.

TypeScript sigue realizando verificaciones de tipos en tiempo de compilación, por lo que conservamos la mayoría de ventajas de tipado estático.

Declaración de tipos unión

Para declarar un tipo unión, se utiliza el operador de barra vertical (|) entre los tipos que se quieren combinar. Esto indica que una variable puede ser de uno u otro tipo.

let valor: number | string;

valor = 42;       // Correcto
valor = "texto";  // Correcto

valor = true;     // ❌ Error: El tipo 'boolean' no se puede asignar al tipo 'number | string'.

En el ejemplo anterior, la variable valor puede contener un número o una cadena de texto, pero no un valor booleano.

Uso de tipos unión en funciones

Las funciones también pueden utilizar tipos unión para sus parámetros y valores de retorno.

function imprimirValor(valor: number | string): void {
    console.log(valor);
}

imprimirValor(123);       // Correcto
imprimirValor("cadena");  // Correcto

imprimirValor(true);   // ❌ Error: El tipo 'boolean' no se puede asignar al tipo 'number | string'.

Tipos unión en propiedades de objetos

Podemos utilizar tipos unión en las propiedades de interfaces y tipos para dotar de flexibilidad de las propiedades dentro de un objeto.

interface Producto {
    id: number;
    nombre: string;
    precio: number | string; // La propiedad precio puede ser un número o una cadena
}

// product1 tiene number como precio
let producto1: Producto = {
    id: 1,
    nombre: "Producto A",
    precio: 100
};

// product1 tiene string como precio
let producto2: Producto = {
    id: 2,
    nombre: "Producto B",
    precio: "Cien dólares"
};

En este ejemplo, la propiedad precio del objeto Producto puede ser un número o una cadena de texto.

Narrowing (reducción de tipos)

Cuando se trabaja con tipos unión, lo normal es que en algún momento tengamos que determinar el tipo específico de una variable en tiempo de ejecución.

Esto se conoce como “narrowing” (reducción de tipos) y se puede lograr utilizando verificaciones de tipo (type guards)

Uso de typeof

El operador typeof es útil para reducir tipos cuando se trabaja con tipos primitivos como números y cadenas de texto.

function procesarValor(valor: number | string): void {
    // Verifica si el valor es un número
    if (typeof valor === "number") {
        console.log(`El valor es un número: ${valor}`);
    } else {
        // Si no es un número, debe ser una cadena de texto
        console.log(`El valor es una cadena: ${valor}`);
    }
}

procesarValor(123);       // El valor es un número: 123
procesarValor("texto");   // El valor es una cadena: texto

Uso de instanceof

El operador instanceof se utiliza para verificar si un objeto es una instancia de una clase específica, lo cual es útil para la reducción de tipos en clases y objetos complejos.

class Perro {
    ladrar() {
        console.log("Guau!");
    }
}

class Gato {
    maullar() {
        console.log("Miau!");
    }
}

function hacerSonido(animal: Perro | Gato): void {
    // Verifica si el animal es una instancia de Perro
    if (animal instanceof Perro) {
        animal.ladrar();  // Si es un Perro, hace ladrar
    } else {
        animal.maullar(); // Si no es un Perro, debe ser un Gato y hace maullar
    }
}

let miPerro = new Perro();
let miGato = new Gato();

hacerSonido(miPerro);  // Guau!
hacerSonido(miGato);   // Miau!

Verificaciones personalizadas

También es posible definir verificaciones de tipo personalizadas (type predicates) para reducir tipos de manera más precisa.

// intarface pajaro
interface Pajaro {
    volar(): void;
    plumas: number;
}

// intarface pez
interface Pez {
    nadar(): void;
    aletas: number;
}

// Función que determina si un animal es un Pajaro
function esPajaro(animal: Pajaro | Pez): animal is Pajaro {
    // Verifica si el método 'volar' está definido en el objeto
    return (animal as Pajaro).volar !== undefined;
}

function hacerAlgo(animal: Pajaro | Pez): void {
    if (esPajaro(animal)) {
        animal.volar();  // Si es un Pajaro, vuela
    } else {
        animal.nadar();  // Si no es un Pajaro, debe ser un Pez y nada
    }
}

Usar alias junto a tipos unión

Puedes utilizar alias de tipo para hacer que los tipos unión sean más claros y concisos.

type ID = number | string;

function obtenerUsuario(id: ID): void {
    // Lógica para obtener usuario
}

En este ejemplo, creamos un Alias ID que puede ser number o string.

Uniones de tipos compuestos

Los tipos unión pueden discriminar incluso cuando los tipos forman parte de otros tipos o objetos.

type ApiResponse = { data: any; error: null } | { data: null; error: string };

function manejarRespuesta(respuesta: ApiResponse): void {
    if (respuesta.error) {
        console.error(`Error: ${respuesta.error}`);
    } else {
        console.log(`Datos: ${respuesta.data}`);
    }
}

let respuesta: ApiResponse = { data: { id: 1, nombre: "Luis" }, error: null };
manejarRespuesta(respuesta);  // Datos: { id: 1, nombre: "Luis" }

En este ejemplo,

  • ApiResponse puede ser una respuesta con datos y sin errores, o sin datos y con errores.
  • El el código maneja ambas posibilidades de manera adecuada.