Utility Types en TypeScript

@zesertebe - blog typescript-utility-types
En el mundo de TypeScript podemos encontrar una gran variedad de utility types que no son otra cosa más que utilidades que facilitan realizar transformaciones entre tipos de TypeScript. El día de hoy veremos la definición de algunos de ellos con sus respectivos ejemplos.

Awaited

Se trata del utility type mas recientemente agregado (al momento de escribir este articulo) ya que se incorporó en la version 4.5 de TypeScript.
Su principal objetivo es modelar el comportamiento de operaciones asíncronas o en promesas irresolubles.

declare function MiPromesa<T>(value: T): T | Promise<T> | PromiseLike<T>;
async function algo(): Promise<[number, number]> {
  const result = await Promise.all([MiPromesa(100), MiPromesa(200)]);
  // Error!
  //
  //    [number | Promise<100>, number | Promise<200>]
  //
  // is not assignable to type
  //
  //    [number, number]
  return result;
}
 

Omit

Permite construir un Type a partir de otro, seleccionando todas las propiedades por defecto y removiendo-omtiendo solo aquellas que se indiquen explícitamente.

// creamos una interfaz llamada Producto y deifnimos algunas propiedades
interface Producto {
    id: number;
    nombre: string;
    valor: number;
  }
  
  // creamos una clase que se encarga de gestionar los productos:
  class ManipularProductos{
    productos: Producto[] = [];
    obtenerProductos(): Producto[]{ return this.productos };
    crearProducto(data: Poducto){
        this.productos.push(data);
        return data;
      }
  }
        
Ahora procedemos a generar un nuevo producto

  const servicio = new ManipularProductos();
  const productos = servicio.obtenerProductos();
  const nuevoProducto = {
      nombre: 'barra de Chocolate',
      valor: 5000
    }
    
  // si tratamos de crear un nuevo producto con el elemento "nuevoProducto" como argumento
  // obtendremos un error ya que el metodo "crearProducto" espera un elemento de tipo "Producto"
  // y en nuestro caso no contamos con el atributo id por lo tanto no es válido: 
  servicio.crearProducto(nuevoProducto);
  /**
   * Argument of type '{nombre: string; valor: number; }' is not assignable to parameter of type 'Producto'
   * Type '{nombre: string; valor: number; }' is missing the following properties from type 'Producto': id
   */
        
En este caso nosotros queremos crear un producto pasando como parametro el nombre y su valor pero no queremos establecer un id ya que esta tarea es responsabilidad del sistema y no de nosotros (Por ejemplo si este servicio se conecta con una base de datos)

Para solucionar este problema podríamos crear un elemento que contenga únicamente aquellos atributos que el usuario deba diligenciar, en este caso solo "nombre" y "valor". Aquí es donde entra en juego omit:

type ProductoDto = Omit<Producto, 'id'>; // indicamos un nuevo tipo omitiendo el atributo id
        
Con esto ya tendríamos solucionado el problema pues ahora con este nuevo tipo podemos indicar que el nuevo producto debe tener todos los atributos EXCEPTO el id. Ahora modificamos un poco el método "crearProducto":

  ...
  crearProducto(data: PoductoDto){
    const nuevoProducto = {
        id: Math.random(),
        ...data
      }
    this.productos.push(nuevoProducto);
     return nuevoProducto;
  }
  ...
        

Partial

Permite generar un nuevo tipo a partir de otro pero estableciendo todas sus propiedades como opcionales. Siguiendo con el ejemplo anterior vamos a crear un nuevo método para actualizar productos pero dado que no necesitamos que todos los campos sean actualizados sino únicamente algunos usaremos el utility type Partial para este fin:

...
type productoParaActualizar = Partial<Producto>;
        
Ahora creamos el método para actualizar los productos y como parámetro pasaremos un elemento de tipo "productoParaActualizar"

...
actualizarProducto(id: number, data: productoParaActualizar){
    const index = this.productos.findIndex(item=> item.id === id);
    this.productos[index] = {
      ...this.productos[index],
      ...data
    }
    return this.productos[index];
  }
        
Ahora si queremos por ejemplo actualizar solamente el valor de un producto podemos hacer lo siguiente:

const datosParaActualizar = {
    valor: 6000
  }
  servicio.actualizarProducto(9, datosParaActualizar);
        

Pick

Al igual que el anterior establece todas las propiedades de un tipo como "opcionales" sin embargo explícitamente nos indica uno o mas campos que deben incluirse:

// todas las propiedades de Producto son opcionales excepto 'valor'
type PickProducto = Pick<Producto, 'valor'>; 
       

Required

Exactamente lo opuesto de Partial, es decir, establece todas las propiedades de un tipo como obligatorias:

interface ProductoOpcional {
    id?: number;
    nombre?: string;
    valor?: number;
  }
// sin errores dado que las propiedades de "ProductoOpcional" son todas opcionales:
const ProductoOpcional01: ProductoOpcional = {valor: 3000};

// aquí obtendremos un error ya que con Required debemos pasar todas las propiedades
const ProductoOpcional02: Required<ProductoOpcional> = {valor: 3000};
         

Readonly

Construye un nuevo tipo cuyos atributos son de "solo lectura", es decir, no pueden ser reasignados:

interface ReadonlyProducto {
      id: number;
      nombre: string;
      valor: number;
  }
  const ProductoReadOnly: Readonly<ReadonlyProducto> = {
      id: 1,
      nombre: 'barra de Chocolate',
      valor: 5000
    }

// obtendremos un error ya que al ser de tipo Readonly no podemos modificar un atributo de este elemento:
ProductoReadOnly.valor = 6000;
     

Record

Permite construir un tipo de objeto cuyas claves son el resultado de mapear las propiedades de un tipo en otro tipo.

interface Producto{
    id: number;
    nombre: string;
    valor: number;
 }
type TipoProductos = "yogurt" | "celular" | "audifonos";
const Productos: Record<TipoProductos, Producto> = {
  yogurt: {id: 1, nombre: "Yogurt Multi Vitaminas", valor: 3000},
  celular: {id: 2, nombre: "Xiamoi", valor: 4000},
  audifonos: {id: 3, nombre: "TWS F9", valor: 5000}
}  
     

Exclued

Construye un tipo a partir de la union de varios tipos excluyendo aquellos que se repiten:

type T0 = Exclude<"a" | "b" | "c", "a">;
// => type T0 = "b" | "c"

type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
// => type T1 = "c"
      

Extract

Dado dos grupos de tipos, mantiene unicamente aquellos que pueden ser asignables en ambos grupos

type T0 = Extract<"a" | "b" | "c", "a" | "f">;
      

NoNullable

Construye un tipo excluyendo aquellos que son de tipo Null o Undefined

type T0 = NonNullable<string | number | undefined>;
// =>   type T0 = string | number
       

Parameters

Construye una tupla de tipos a partir de los tipos usados en los parámetros de una función

type T1 = Parameters<(s: string) => void>;
// => T1 = [s: string]
         

ReturnType

Construye un tipo consistente con el tipo de retorno de una función

type T0 = ReturnType<() => string>;
// => T0 = string