TypeScript incluye un conjunto de tipos genéricos llamados utility types, que transforman tipos existentes en tipos nuevos. Forman parte de la biblioteca estándar, así que no necesitas instalar nada adicional. Aprenderlos reemplaza una gran cantidad de definiciones de tipo repetitivas y manuales.
Introducción a los Utility Types
Los utility types son tipos genéricos predefinidos que realizan transformaciones de tipo comunes. Te permiten derivar un nuevo tipo a partir de uno existente en lugar de escribirlo desde cero. Una vez que empiezas a usarlos, dejas de copiar y pegar definiciones de interfaz con ligeras variaciones.
Partial<>
Partial<> toma un tipo y hace que todas sus propiedades sean opcionales. El caso de uso clásico son las funciones de actualización que aceptan un subconjunto de campos.
interface User {
id: number;
name: string;
email: string;
}
function updateUser(id: number, user: Partial<User>) {
// Implementation to update user
}
updateUser ahora acepta cualquier combinación de propiedades de User sin requerirlas todas.
Required<>
Required<> es el inverso de Partial<>. Toma un tipo donde algunas o todas las propiedades son opcionales y marca cada una como obligatoria.
interface User {
id?: number;
name?: string;
email?: string;
}
type CompleteUser = Required<User>;
CompleteUser requiere los tres campos, sin importar la opcionalidad en la interfaz de origen.
Readonly<>
Readonly<> hace que todas las propiedades de un tipo no se puedan escribir después de la inicialización. Útil para objetos de configuración y otros datos que no deberían mutarse.
interface User {
id: number;
name: string;
}
const user: Readonly<User> = {
id: 1,
name: 'John Doe',
};
// This will cause an error:
// user.name = 'Jane Doe';
Pick<>
Pick<> construye un nuevo tipo a partir de un subconjunto de las propiedades de un tipo existente. Práctico cuando solo necesitas parte de una interfaz más grande.
interface User {
id: number;
name: string;
email: string;
isAdmin: boolean;
}
type Admin = Pick<User, 'id' | 'isAdmin'>;
const adminUser: Admin = {
id: 1,
isAdmin: true,
};
Omit<>
Omit<> es el complemento de Pick<>. Produce un tipo que excluye propiedades específicas. Habitual para eliminar campos sensibles antes de enviar datos a los clientes.
interface User {
id: number;
name: string;
email: string;
password: string;
}
type PublicUser = Omit<User, 'password'>;
const publicUser: PublicUser = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
};
Record<>
Record<> crea un tipo de objeto con un conjunto fijo de claves y un tipo de valor uniforme. Es la opción correcta para tablas de búsqueda tipadas.
type Roles = 'admin' | 'user' | 'guest';
interface Permissions {
canEdit: boolean;
canDelete: boolean;
}
type RolePermissions = Record<Roles, Permissions>;
const permissions: RolePermissions = {
admin: { canEdit: true, canDelete: true },
user: { canEdit: true, canDelete: false },
guest: { canEdit: false, canDelete: false },
};
Exclude<>
Exclude<> filtra un tipo de unión eliminando los miembros que coinciden con un tipo dado.
type Status = 'success' | 'error' | 'pending';
type NonErrorStatus = Exclude<Status, 'error'>;
// NonErrorStatus is 'success' | 'pending'
Extract<>
Extract<> es lo contrario: conserva solo los miembros de la unión que coinciden con un tipo dado.
type Status = 'success' | 'error' | 'pending';
type ErrorStatus = Extract<Status, 'error' | 'pending'>;
// ErrorStatus is 'error' | 'pending'
NonNullable<>
NonNullable<> elimina null y undefined de un tipo. Útil en la frontera entre datos que pueden ser nulos y código que espera un valor concreto.
type Name = string | null | undefined;
type ValidName = NonNullable<Name>;
// ValidName is string
ReturnType<>
ReturnType<> infiere el tipo de retorno de una función. Esto convierte a la función en la única fuente de verdad sobre la forma de su salida.
function getUser() {
return { id: 1, name: 'John Doe' };
}
type User = ReturnType<typeof getUser>;
// User is { id: number; name: string; }
InstanceType<>
InstanceType<> extrae el tipo de instancia a partir de una función constructora. Útil cuando pasas constructores de clase y necesitas referenciar el tipo de lo que producen.
class User {
constructor(public id: number, public name: string) {}
}
type UserInstance = InstanceType<typeof User>;
// UserInstance is User
Conclusión
Los utility types son una de las partes más prácticas del sistema de tipos de TypeScript. Solo Partial<> y Omit<> ya cubren la mayoría de los casos que encontrarás al modelar entradas de API y formatos de datos públicos. Vale la pena conocer los demás para reconocer cuándo aplican, en lugar de escribir definiciones de tipo redundantes a mano.
El error más común es escribir una nueva interfaz cuando un utility type aplicado a una existente haría el trabajo con menos código y mejor acoplamiento al tipo de origen.