A estas alturas de la película la mayoría ya conocemos al bueno de TypeScript. Llegó al día a día de muchos gracias a Angular, uno de los frameworks web más utilizados. Sin embargo, muchos usuarios se limitan a pensar en él como un JavaScript con tipos estáticos y no prestan atención a característas muy interesantes como la que ocupa este artículo: los Utility Types.
¿Qué son los Utility Types?
El sistema de tipos de TypeScript no ha dejado de ampliarse versión a versión. La primera aparición de nuestro tema de hoy es en la versión 2.1, allá por noviembre de 2016. Se presentaban en sociedad Partial
, Readonly, Record y Pick
. Estos constructos, junto con algunos operadores y nueva sintaxis que los acompañaban, tenían el proposito de simplificar la generación de tipos de datos a partir de otros tipos de datos, una operación que realizamos con frecuencia.
Vamos a ver algún ejemplo antes de ponerme más pesado:
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
todo.title = "Hello";
// Cannot assign to 'title' because it is a read-only property.
Readonly<T>
recibe un tipo cualquiera (T
), en nuestro ejemplo el tipo Todo
, y genera para nosotros un tipo idéntico pero en el que todos los campos se definen como de solo lectura. Así nos podemos ayudar de estos tipos para evitar escribir código redundante y dejar de tener un número creciente de interfaces que en realidad solo son distintas versiones de la misma entidad en nuestro dominio.
Soy plenamente consciente de que Readonly<T>
está lejos de ser espectacular y quizá no consiga que nadie preste atención a esta característica del lenguaje con él. Pero oye, a mi me gusta empezar las cosas por el principio, no seamos impacientes y preguntémonos ahora hacia dónde ha evolucionado la cosa en los últimos años.
Los tipos más útiles
Omit<Type, Keys>
Vamos a ampliar la interfaz anterior, nuestro tipo Todo
(ahora un poco más realista) contiene, además del título, una descripción, un campo que indica si ha sido completado, un identificador único y la fecha de creación.
interface Todo {
id: string;
title: string;
description: string;
completed: boolean;
createdAt: number;
}
Ahora tenemos un pequeño inconveniente, cuando un usuario intente crear uno de estos objetos lo normal es que no aporte los campos id
y createdAt
. Una tentación en estos casos es hacer que ambos campos sean opcionales o pasar un valor que sugiera que el campo no se ha aportado, por ejemplo, una cadena de caracteres vacía, un 0 o -1. Otra alternativa, mucho más correcta que las anteriores, sería crear un nuevo tipo que represente un objeto Todo
que estamos creando.
Para crear ese nuevo tipo, que depende del original, Omit
nos viene de perlas. Podemos usarlo para generar un nuevo tipo que omite, que no incluye, algunos de los campos en el tipo original.
interface Todo {
id: string;
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoDraft = Omit<Todo, 'id' | 'createdAt'>;
El tipo TodoDraft
se genera aportando el tipo Todo
e indicando uno o varios campos a excluir (utilizando una unión de tipos con el operador |
). Así se obtiene un nuevo tipo adecuado para representar el objeto cuando se está creando, no se necesita volver a escribir cada uno de los campos y si cambia la interfaz original, cambia también TodoDraft
.
Parameters<Type>
Con Parameters se recuperan facilmente en una tupla los parámetros de una función:
function add(a: number, b: string, c:boolean): string {
return a + b;
}
type AddReturnType = Parameters<typeof add>;
// type AddReturnType = [a: number, b: string, c:boolean];
ReturnType<Type>
y Awaited<Type>
A día de hoy es la norma trabajar con funciones asíncronas, empleando conjuntamente ReturnType y Awaited se puede obtener el tipo al que resuleve una función que devuelve una promesa facilmente.
async function fetchData(): Promise<string> {
// fetch data from API and return a string
}
type ResolvedResult = Awaited<ReturnType<typeof fetchData>>;
// type ResolvedResult = string
Ya me voy…
No dejes de visitar la documentación para ver si alguno de estos tipos puede solucionarte alguna papeleta. Tampoco te vayas sin echar un vistazo a otros artículos del blog ¡Nos leeremos en otra ocasión!