馃攽 Programando tipos en Typescript – Parte I

Typescript es un sistema de tipos de datos que opera alrededor de Javascript. Nos ofrece la capacidad de validar la definici贸n del c贸digo que escribimos en este lenguaje.

No opera en tiempo de ejecuci贸n, es decir, no es capaz de validar los datos que son recibidos o exportados desde una funci贸n en Javascript.

La principal caracter铆stica es que refuerza las definiciones del c贸digo en Javascript y permite evitar errores de acceso, definici贸n y l贸gica.

Un sencillo ejemplo se puede observar a continuaci贸n

type Person = {
  name: string;
  age: number;
};

function getName(p: Person) {
  return p.firstname; //error: Property firstname does not exist on type Person
}

En este ejemplo Typescript nos ayuda a utilizar correctamente la definici贸n de la entidad sobre la que queremos operar.

Nota: todos los ejemplos se encuentran en este tablero de repl.it.


Typescript permite tambi茅n obtener nuevos tipos de datos a partir de tipos de datos. Esto se puede realizar de diversas formas.

Extendiendo tipos con gen茅ricos

La forma b谩sica de obtener nuevos tipos de datos es mediante el uso de gen茅ricos. Con estos podemos extender definiciones y utilizarlas de diversas formas dependiendo de su contexto.

Por ejemplo extenderemos nuestra definici贸n anterior para Person con un argumento gen茅rico para la direcci贸n, que en este caso puede ser s贸lo una cadena de caracteres o algo m谩s complejo.

// El argumento gen茅rico `AddressType` define el tipo de datos de la propiedad `address`
type Person<AddressType> = {
  name: string;
  age: number;
  address: AddressType;
}

// Este tipo de Persona tiene una direcci贸n sencilla
type PersonAddressSingleLine = Person<string>;

// Este tipo de datos define a una direcci贸n con varias propiedades
type Address = {
  street: string;
  number: number;
  zipcode: string;
  country: string;
}

// Este tipo de Persona tiene una direcci贸n m谩s completa
type PersonComplexAddress = Person<Address>;

function getSimpleAddress(p: PersonAddressSingleLine) {
  return p.address;
}

function getPersonsCountry(p: PersonComplexAddress) {
  return p.address.country;
}

De esta forma podemos reutilizar la definici贸n de Persona que hemos creado y adaptarla a las necesidades de nuestros casos de uso.

Si intentamos asignar alg煤n argumento que no cumple con la definici贸n del tipo de datos Typescript se encargar谩 de marcar el error indicando una incompatibilidad.

Restricciones para gen茅ricos

Los argumentos gen茅ricos no s贸lo funcionan para definir tipos, tambi茅n pueden ser utilizados para funciones y para crear otros tipos mediante comparaciones. Estas comparaciones se realizan mendiante el uso de la palabra reservada extends, la cual provee l铆mites sobre los que un tipo gen茅rico puede operar.

// Definimos una funci贸n en donde devolvemos el c贸digo postal
// de cualquier entidad con una direcci贸n del tipo `Address`.
function getAnyonesZipcode<T extends { address: Address }>(p: T) {
  console.log("Zipcode", p.address.zipcode);
  return p.address.zipcode;
}

// Una organizaci贸n tambi茅n cuenta con una direcci贸n
type Organization = {
  name: string;
  address: Address;
}

// Dame el c贸digo postal de una organizaci贸n
const getOrganizationZipCode = (o: Organization) => getAnyonesZipcode(o);

// Dame el c贸digo postal de una persona
const getPersonsZipcode = (p: PersonComplexAddress) => getAnyonesZipcode(p);

En el ejemplo anterior la funci贸n getAnyonesZipcode cuenta con una restricci贸n definida mediante T extends {address: Address} que indica que puede operar sobre todo tipo T que tenga una propiedad address que sea del tipo Address (definido previamente).

De esta forma podemos construir funciones que operan sobre tipos de datos espec铆ficos, Organization y PersonAddressMultiProperties, las cu谩les cumplen con dicha propiedad.

Si no cumplimos con dicha restricci贸n Typescript nos lo indicar谩 adecuadamente:

// error: Types of property address are incompatible
const getSimpleZipcode = (p: PersonAddressSingleLine) => getAnyonesZipcode(p);

Los tipos que declaramos tambi茅n pueden ser uniones de varios tipos, por ejemplo:

type AddressTypes = { address: string } | { address: Address };

Este nuevo tipo tambi茅n puede ser usado para limitar argumentos gen茅ricos y utilizar las propiedades de la entidad de forma correcta.

function someAddress<T extends AddressTypes>(p: T) {
  if ("string" === typeof p.address) {
    console.log("Simple Address:", p.address);
  } else {
    console.log("Complex Address => { country:", p.address.country, ", ...}");
  }
}

someAddress({ address: "Calle nueva 1" });
someAddress({
  address: {
    street: "Calle Norte",
    number: 1,
    zipcode: "701190-A",
    country: "M茅xico"
  }
});

// error: tipos incompatibles
someAddress("algo no bueno");
someAddress({ address: 345 }) 

La parte m谩s interesante de extends es que tambi茅n puede ser usada para crear un nuevo tipo evaluando un argumento gen茅rico de otro tipo

// Un tipo de datos que indica si el gen茅rico cumple con la restricci贸n
type IsAddressable<T> = T extends { address: Address } ? true : false;

// true
type IsOrgAddressable = IsAddressable<Organization>;

//false
type IsSimpleAddressable = IsAddressable<PersonAddressSingleLine>;

鈿狅笍 Lamentablemente estos tipos no son de mucha utilidad fuera de una definici贸n de los datos ya que no pueden ser usados para evaluar datos en tiempo de ejecuci贸n, es decir, no podemos corroborar con estas definiciones si nuestra informaci贸n cumple o no con ciertas caracter铆sticas.

A煤n as铆 podemos usar esta propiedad de extends para crear tipos que definan arreglos de elementos que contienen direcciones:

// define un tipo que cuando recibe una entidad con una direcci贸n
// la convierte en un tipo que implementa un arreglo
type ManyAddresses<T> = T extends AddressTypes ? T[] : never;

// Arreglo de direcciones simples
type ManySimple = ManyAddresses<{ address: string }>;

// Arreglo de direcciones complejas
type ManyComplex = ManyAddresses<{ address: Address }>;

// Una funci贸n que retorna un arreglo de direcciones a partir de una semilla
function getManyAddresses<T extends AddressTypes>(seed: T): ManyComplex | ManySimple {
  if ("string" === typeof seed.address) {
    return [
      {
        address: seed.address + + "Simple one"
      },
      {
        address: seed.address + + "Simple two"
      },
    ]
  }

  return [
    {
      address: {
        street: seed.address.street,
        number: 1,
        zipcode: "701190-A",
        country: "M茅xico"
      }
    },
    {
      address: {
        street: seed.address.street,
        number: seed.address.number + 100,
        zipcode: "701190-B",
        country: "M茅xico"
      }
    }
  ]
}

console.log(getManyAddresses({
  name: "Vieja f谩brica de clavicornios",
  address: {
    street: "Calle Norte",
    number: 1,
    zipcode: "701190-A",
    country: "M茅xico"
  }
}));

Como se puede observar en estos ejemplos el sistema de tipos de Typescript es una herramienta que permite definir y derivar nuevos tipos de datos a partir de otros ya definidos.

En otro art铆culo observaremos m谩s utilidades que nos permiten seguir extendiendo nuestras definiciones.

1 Comment

Write a comment