Utility Types de TypeScript que deberías conocer

Close-up of 'Ctrl' and 'Z' keyboard keys on a vibrant coral background. Ideal for tech and design themes.
Foto de Miguel Á. Padriñán en Pexels

En el día a día de un proyecto TypeScript nos encontramos con objetos que cambian de forma según el contexto: al crear, actualizar o leer datos, o al exponer una API. Los utility types son herramientas del lenguaje que nos permiten transformar tipos de forma declarativa, evitando código repetitivo y errores comunes.

Partial

Hace que todas las propiedades de un tipo sean opcionales. Es ideal para representar actualizaciones parciales de recursos.

type User = { id: number; name: string; email: string; };
type UserUpdate = Partial<User>;

// UserUpdate equivale a { id?: number; name?: string; email?: string; }

Pick y Omit

Pick crea un nuevo tipo seleccionando sólo algunas propiedades, mientras que Omit hace lo contrario, excluyendo las que no queremos.

type User = { id: number; name: string; email: string; role: string; };

// Sólo id y name
type UserIdName = Pick<User, 'id' | 'name'>;

// Todo menos el email
type UserWithoutEmail = Omit<User, 'email'>;

Record

Construye un tipo cuyas claves son de un conjunto específico y cuyos valores comparten el mismo tipo. Muy útil para mapear enums a valores.

enum Color { Red = 'red', Green = 'green', Blue = 'blue' }

type ColorHex = Record<Color, string>;

// Resulta en { red: string; green: string; blue: string; }

Required y Readonly

Required vuelve obligatorias todas las propiedades de un tipo, mientras que Readonly las convierte en de solo lectura.

type PartialUser = { id?: number; name?: string; };

type CompleteUser = Required<PartialUser>; // { id: number; name: string; }

type ImmutableUser = Readonly<CompleteUser>; // todas las propiedades son readonly

ReturnType y Parameters

Permiten extraer, respectivamente, el tipo de retorno y los tipos de los parámetros de una función. Son la base de muchas abstracciones avanzadas.

function fetchUser(id: number): Promise<User> { / ... / }

type FetchUserReturn = ReturnType<typeof fetchUser>; // Promise

type FetchUserParams = Parameters<typeof fetchUser>; // [number]

Exclude, Extract y NonNullable

Estos tres tipos operan sobre uniones. Exclude elimina miembros, Extract conserva sólo los que coinciden, y NonNullable elimina null y undefined.

type All = string | number | null | undefined;

type NoNull = NonNullable<All>; // string | number

type Numbers = Extract<All, number>; // number

type NoString = Exclude<All, string>; // number | null | undefined

Uppercase y Lowercase

Transforman literales de cadena a su versión en mayúsculas o minúsculas, lo que facilita la normalización de claves.

type Foo = 'hello' | 'world';

type UpperFoo = Uppercase<Foo>; // 'HELLO' | 'WORLD'

type LowerFoo = Lowercase<UpperFoo>; // 'hello' | 'world'

Awaited

Desenvuelve el tipo que resulta de await sobre una promesa. Muy práctico cuando trabajamos con funciones asíncronas genéricas.

async function getData(): Promise<{ data: string }> { / ... / }

type Resolved = Awaited<ReturnType<typeof getData>>; // { data: string }

Estos utility types son sólo la punta del iceberg; TypeScript incluye muchos más y la comunidad sigue creando variantes útiles. Aprender a combinarlos te permite escribir APIs tipadas de forma mucho más expresiva, reducir la duplicación de tipos y, sobre todo, evitar errores que en tiempo de ejecución serían difíciles de detectar.

Mi consejo práctico: cuando necesites crear un nuevo tipo a partir de otro, primero revisa si alguno de los utility types estándar ya cubre tu caso. Ahorrarás tiempo y mantendrás tu código más legible para todo el equipo.