
Partial y Required
Los utility types Partial<T> y Required<T> modifican la obligatoriedad de todas las propiedades de un tipo. Son operaciones inversas entre si.
Partial
Partial<T> convierte todas las propiedades de T en opcionales:
interface Usuario {
id: number
nombre: string
email: string
edad: number
}
type UsuarioParcial = Partial<Usuario>
// { id?: number; nombre?: string; email?: string; edad?: number }
El caso de uso principal es la actualización parcial de objetos, donde solo se proporcionan los campos que cambian:
function actualizarUsuario(id: number, cambios: Partial<Usuario>): Usuario {
const usuarioActual: Usuario = {
id,
nombre: "Ana",
email: "ana@ejemplo.com",
edad: 30
}
return { ...usuarioActual, ...cambios }
}
const actualizado = actualizarUsuario(1, { nombre: "Ana Maria" })
console.log(actualizado.nombre) // "Ana Maria"
console.log(actualizado.email) // "ana@ejemplo.com" (preservado)
Partial es útil también para opciones de configuración con valores por defecto:
interface OpcionesConexion {
host: string
puerto: number
timeout: number
reintentos: number
}
const OPCIONES_POR_DEFECTO: OpcionesConexion = {
host: "localhost",
puerto: 3000,
timeout: 5000,
reintentos: 3
}
function conectar(opciones: Partial<OpcionesConexion> = {}): OpcionesConexion {
return { ...OPCIONES_POR_DEFECTO, ...opciones }
}
const config = conectar({ puerto: 8080, timeout: 10000 })
console.log(config.host) // "localhost"
console.log(config.puerto) // 8080
console.log(config.reintentos) // 3
Required
Required<T> hace todas las propiedades obligatorias, eliminando los modificadores ?:
interface Formulario {
nombre?: string
email?: string
telefono?: string
}
type FormularioCompleto = Required<Formulario>
// { nombre: string; email: string; telefono: string }
function enviarFormulario(datos: Required<Formulario>): void {
console.log(`Enviando: ${datos.nombre}, ${datos.email}, ${datos.telefono}`)
}
// Error: falta la propiedad "telefono"
// enviarFormulario({ nombre: "Luis", email: "luis@ejemplo.com" })
enviarFormulario({ nombre: "Luis", email: "luis@ejemplo.com", telefono: "600123456" })
Requiredes especialmente útil cuando un tipo base tiene propiedades opcionales para la creación, pero en cierto punto del flujo todas deben estar presentes.
Readonly
Readonly<T> convierte todas las propiedades en solo lectura, impidiendo su reasignación:
interface Estado {
contador: number
activo: boolean
items: string[]
}
const estado: Readonly<Estado> = {
contador: 0,
activo: true,
items: ["a", "b"]
}
// Error: no se puede asignar a propiedad readonly
// estado.contador = 1
// estado.activo = false
Readonlysolo protege el primer nivel de propiedades. Los objetos y arrays anidados siguen siendo mutables internamente. Para inmutabilidad profunda es necesario crear un tipoDeepReadonlypersonalizado.
function congelar<T extends object>(obj: T): Readonly<T> {
return Object.freeze(obj)
}
const config = congelar({ api: "https://api.ejemplo.com", version: 2 })
console.log(config.api) // "https://api.ejemplo.com"
// config.api = "otra" // Error en tiempo de compilación
Pick y Omit
Pick<T, K> construye un tipo seleccionando solo las propiedades indicadas. Omit<T, K> hace lo contrario, excluyendo las propiedades indicadas.
Pick
interface Producto {
id: number
nombre: string
descripcion: string
precio: number
stock: number
categoria: string
}
type ProductoResumen = Pick<Producto, "id" | "nombre" | "precio">
// { id: number; nombre: string; precio: number }
type ProductoInventario = Pick<Producto, "id" | "nombre" | "stock">
// { id: number; nombre: string; stock: number }
function mostrarResumen(producto: ProductoResumen): void {
console.log(`${producto.nombre}: ${producto.precio} EUR`)
}
mostrarResumen({ id: 1, nombre: "Monitor", precio: 300 })
Omit
type ProductoSinId = Omit<Producto, "id">
// { nombre: string; descripcion: string; precio: number; stock: number; categoria: string }
type CrearProducto = Omit<Producto, "id" | "stock">
// { nombre: string; descripcion: string; precio: number; categoria: string }
function crearProducto(datos: CrearProducto): Producto {
return {
...datos,
id: Math.floor(Math.random() * 10000),
stock: 0
}
}
const nuevo = crearProducto({
nombre: "Teclado",
descripcion: "Teclado mecánico",
precio: 75,
categoria: "Perifericos"
})
console.log(nuevo.id) // número aleatorio
console.log(nuevo.stock) // 0
Combinación de Pick y Omit
interface Empleado {
id: number
nombre: string
email: string
departamento: string
salario: number
fechaAlta: Date
}
// Solo datos publicos
type EmpleadoPublico = Omit<Empleado, "salario" | "fechaAlta">
// Solo datos de contacto
type Contacto = Pick<Empleado, "nombre" | "email">
// Para actualización: sin id ni fechaAlta, y parcial
type ActualizarEmpleado = Partial<Omit<Empleado, "id" | "fechaAlta">>
function actualizar(id: number, cambios: ActualizarEmpleado): void {
console.log(`Actualizando empleado ${id}:`, cambios)
}
actualizar(1, { nombre: "Ana Garcia", departamento: "IT" })
Record
Record<K, V> construye un tipo de objeto cuyas claves son de tipo K y cuyos valores son de tipo V:
type Rol = "admin" | "editor" | "lector"
type Permisos = Record<Rol, boolean>
// { admin: boolean; editor: boolean; lector: boolean }
const permisos: Permisos = {
admin: true,
editor: true,
lector: false
}
Record es ideal para crear mapas tipados y diccionarios:
type CodigoHTTP = 200 | 201 | 400 | 404 | 500
const mensajesHTTP: Record<CodigoHTTP, string> = {
200: "OK",
201: "Creado",
400: "Peticion incorrecta",
404: "No encontrado",
500: "Error interno del servidor"
}
console.log(mensajesHTTP[404]) // "No encontrado"
interface ConfiguracionModulo {
activo: boolean
prioridad: number
opciones: Record<string, unknown>
}
type Modulos = "auth" | "cache" | "logger"
const configuracion: Record<Modulos, ConfiguracionModulo> = {
auth: { activo: true, prioridad: 1, opciones: { secreto: "abc" } },
cache: { activo: true, prioridad: 2, opciones: { ttl: 3600 } },
logger: { activo: false, prioridad: 3, opciones: { nivel: "info" } }
}
Exclude y Extract
Exclude<T, U> elimina de una unión los miembros asignables a U. Extract<T, U> hace lo contrario, conservando solo los miembros asignables a U.
type Evento = "click" | "scroll" | "keydown" | "keyup" | "focus" | "blur"
type EventoTeclado = Extract<Evento, "keydown" | "keyup">
// "keydown" | "keyup"
type EventoSinTeclado = Exclude<Evento, "keydown" | "keyup">
// "click" | "scroll" | "focus" | "blur"
Estos tipos operan sobre uniones de cualquier tipo, no solo strings:
type Dato = string | number | boolean | null | undefined
type DatoDefinido = Exclude<Dato, null | undefined>
// string | number | boolean
type DatoPrimitivo = Extract<Dato, string | number | boolean>
// string | number | boolean
type Forma =
| { tipo: "circulo"; radio: number }
| { tipo: "rectangulo"; ancho: number; alto: number }
| { tipo: "triangulo"; base: number; altura: number }
type FormaConArea = Extract<Forma, { tipo: "circulo" } | { tipo: "rectangulo" }>
// { tipo: "circulo"; radio: number } | { tipo: "rectangulo"; ancho: number; alto: number }
NonNullable
NonNullable<T> elimina null y undefined de una unión de tipos:
type MaybeString = string | null | undefined
type DefiniteString = NonNullable<MaybeString>
// string
interface Respuesta {
datos: string | null
error: string | undefined
}
function procesar(datos: NonNullable<Respuesta["datos"]>): void {
// datos es string, nunca null
console.log(datos.toUpperCase())
}
function validarRespuesta(resp: Respuesta): void {
if (resp.datos != null) {
procesar(resp.datos) // TypeScript sabe que no es null aquí
}
}
Parameters y ReturnType
Parameters<T> extrae los tipos de los parámetros de una función como una tupla. ReturnType<T> extrae el tipo de retorno.
function crearUsuario(nombre: string, edad: number, activo: boolean): {
id: number
nombre: string
edad: number
activo: boolean
} {
return { id: Date.now(), nombre, edad, activo }
}
type ParamsCrear = Parameters<typeof crearUsuario>
// [string, number, boolean]
type ResultadoCrear = ReturnType<typeof crearUsuario>
// { id: number; nombre: string; edad: number; activo: boolean }
Estos tipos son especialmente útiles para crear wrappers y decoradores:
function conLog<T extends (...args: any[]) => any>(
fn: T,
nombre: string
): (...args: Parameters<T>) => ReturnType<T> {
return (...args: Parameters<T>): ReturnType<T> => {
console.log(`Llamando a ${nombre} con:`, args)
const resultado = fn(...args)
console.log(`${nombre} devolvio:`, resultado)
return resultado
}
}
function sumar(a: number, b: number): number {
return a + b
}
const sumarConLog = conLog(sumar, "sumar")
sumarConLog(3, 5)
// Llamando a sumar con: [3, 5]
// sumar devolvio: 8
type Manejador = (req: { url: string; metodo: string }, res: { send: (d: string) => void }) => void
type ParamsManejador = Parameters<Manejador>
// [{ url: string; metodo: string }, { send: (d: string) => void }]
type RetornoManejador = ReturnType<Manejador>
// void
InstanceType
InstanceType<T> extrae el tipo de instancia de un constructor:
class Conexion {
constructor(public host: string, public puerto: number) {}
conectar(): void {
console.log(`Conectando a ${this.host}:${this.puerto}`)
}
}
type TipoConexion = InstanceType<typeof Conexion>
// Conexion
function crearInstancia<T extends new (...args: any[]) => any>(
Constructor: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new Constructor(...args)
}
const conn = crearInstancia(Conexion, "localhost", 5432)
conn.conectar() // "Conectando a localhost:5432"
InstanceType es útil en patrones donde se pasan clases como parámetros y se necesita tipar las instancias resultantes:
class Logger {
log(mensaje: string): void { console.log(mensaje) }
}
class Cache {
datos = new Map<string, unknown>()
obtener(clave: string): unknown { return this.datos.get(clave) }
}
type Servicio = typeof Logger | typeof Cache
function inicializar<T extends new () => any>(
servicios: T[]
): InstanceType<T>[] {
return servicios.map(S => new S())
}
const [logger, cache] = inicializar([Logger, Cache])
Awaited
Awaited<T> desempaqueta el tipo de una Promise, incluso promesas anidadas. Es el tipo que resulta de usar await:
type A = Awaited<Promise<string>>
// string
type B = Awaited<Promise<Promise<number>>>
// number
type C = Awaited<string>
// string (no es Promise, devuelve T directamente)
Awaited es útil para tipar resultados de funciones asíncronas:
async function obtenerDatos(): Promise<{ id: number; nombre: string }[]> {
const res = await fetch("/api/datos")
return res.json()
}
type Datos = Awaited<ReturnType<typeof obtenerDatos>>
// { id: number; nombre: string }[]
function procesarPromesas<T extends Promise<any>[]>(
...promesas: T
): Promise<{ [K in keyof T]: Awaited<T[K]> }> {
return Promise.all(promesas) as any
}
async function ejemplo() {
const [usuario, config] = await procesarPromesas(
Promise.resolve({ nombre: "Ana" }),
Promise.resolve({ tema: "oscuro" })
)
console.log(usuario.nombre) // "Ana"
console.log(config.tema) // "oscuro"
}
Combinaciones prácticas de utility types
Los utility types alcanzan su máximo potencial cuando se combinan para modelar transformaciones complejas:
interface Articulo {
id: number
titulo: string
contenido: string
autor: string
etiquetas: string[]
publicado: boolean
fechaCreacion: Date
}
// Para crear: sin id ni fecha, contenido y etiquetas opcionales
type CrearArticulo = Omit<Articulo, "id" | "fechaCreacion"> &
Partial<Pick<Articulo, "contenido" | "etiquetas">>
// Para listar: solo datos básicos
type ArticuloLista = Pick<Articulo, "id" | "titulo" | "autor" | "publicado">
// Para editar: todo parcial excepto id
type EditarArticulo = Partial<Omit<Articulo, "id">> & Pick<Articulo, "id">
function crear(datos: CrearArticulo): Articulo {
return {
...datos,
id: Date.now(),
contenido: datos.contenido ?? "",
etiquetas: datos.etiquetas ?? [],
fechaCreacion: new Date()
}
}
const articulo = crear({
titulo: "Utility types en TypeScript",
autor: "Elena",
publicado: false
})
console.log(articulo.id) // timestamp
console.log(articulo.etiquetas) // []
console.log(articulo.fechaCreacion) // Date actual
// Tipo que hace readonly solo ciertas propiedades
type ReadonlyPick<T, K extends keyof T> =
Readonly<Pick<T, K>> & Omit<T, K>
interface Documento {
id: number
titulo: string
contenido: string
version: number
}
type DocConIdFijo = ReadonlyPick<Documento, "id" | "version">
const doc: DocConIdFijo = { id: 1, titulo: "Doc", contenido: "...", version: 1 }
doc.titulo = "Nuevo titulo" // OK
// doc.id = 2 // Error: readonly
// doc.version = 2 // Error: readonly
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en TypeScript
Documentación oficial de TypeScript
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Aplicar Partial, Required y Readonly para modificar propiedades de tipos. Usar Pick, Omit y Record para crear tipos derivados. Extraer tipos con Exclude, Extract, NonNullable, Parameters, ReturnType, InstanceType y Awaited.