Menu

Partial y Required en TypeScript: ejemplos y DeepPartial

Partial<T> hace opcionales todas las propiedades de T, que es justo el tipo de un objeto de actualización o patch. Aprende Partial en funciones de actualización, por qué es superficial, cómo escribir un DeepPartial, el problema del undefined explícito y su opuesto, Required<T>.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Partial<T> es un utility type integrado que hace opcionales todas las propiedades de T. Es el tipo natural para una actualización o un patch: quien llama envía solo los campos que cambiaron.

Partial<User> es { id?: number; name?: string; email?: string }. El compilador sigue comprobando los campos que sí pasas: { nmae: "x" } o { name: 42 } es un error, y eso es lo que hace a Partial mejor que un parámetro object o any sin más.

Cómo está definido Partial

Partial es un mapped type de una línea de la librería estándar de TypeScript:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

Para cada clave P de T, declara una propiedad opcional con el mismo tipo. Como recorre keyof T, conserva el readonly de las propiedades que lo tenían. Al ser un tipo normal, no tiene efecto en ejecución: el objeto que se pasa como changes es el mismo en cualquier caso.

Leer de un Partial da T | undefined

Cualquier propiedad de un Partial<T> puede faltar, así que leer una da el tipo de la propiedad más undefined. El compilador te obliga a tratar el caso en que falta:

{ ...defaults, ...opts } es la forma habitual de convertir un Partial<Options> de nuevo en un Options completo: primero el spread de los valores por defecto y después los valores recibidos, que los sobrescriben.

El problema del undefined explícito

Una propiedad opcional puede faltar, pero también puede estar presente con el valor undefined. El spread de objetos copia ese undefined encima del valor real, y el tipo del resultado no lo refleja:

Esto da problemas cuando un patch se construye a partir de un formulario o de una query string en los que los campos vacíos se convierten en undefined. La opción del compilador exactOptionalPropertyTypes (que no forma parte de strict) convierte { name: undefined } en un error de compilación para name?: string, salvo que escribas name?: string | undefined, y así lo evita desde el origen.

Partial es superficial

Partial solo hace opcionales las propiedades del primer nivel. Un objeto anidado, si lo pasas, tiene que estar completo:

index.ts(8,3): error TS2741: Property 'tabSize' is missing in type '{ fontSize: number; }' but required in type '{ fontSize: number; tabSize: number; }'.

La propiedad editor es opcional, pero cuando está presente tiene el tipo { fontSize: number; tabSize: number }, sin cambios. Para objetos de configuración, patches de API y datos de prueba muchas veces quieres propiedades opcionales en todos los niveles. Eso necesita un tipo recursivo.

DeepPartial: un Partial recursivo

TypeScript no trae una versión profunda integrada, pero son pocas líneas. Las funciones y los arrays se dejan como están, porque hacer opcionales los elementos de un array permitiría [undefined]:

El tipo es recursivo; la fusión no. applySettings fusiona editor a mano porque el spread de objetos también es superficial. Existen funciones genéricas de fusión profunda en librerías como lodash (merge), y tiparlas es más difícil que el tipo de arriba.

Required: el opuesto de Partial

Required<T> quita el ? de todas las propiedades. Está definido con el modificador -?, que además quita undefined del tipo de cada propiedad:

El patrón es el mismo que con Partial, al revés: quien usa una API pasa una configuración flexible, y el código interno trabaja con una versión Required en la que se sabe que existen todos los valores. El spread tiene el mismo agujero que la función de actualización de antes: quien pasa port: undefined de forma explícita sobrescribe el valor por defecto con undefined, y el compilador lo acepta salvo que exactOptionalPropertyTypes esté activado. Required es superficial igual que Partial.

Hacer opcionales u obligatorias solo algunas propiedades

Partial y Required se aplican a todas las propiedades. Para cambiar solo unas pocas, divide el tipo con Pick y Omit y vuelve a juntarlo:

type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

interface Post {
  id: number;
  title: string;
  body?: string;
}

type NewPost = PartialBy<Post, "id">;      // id optional, title required, body optional
type Published = RequiredBy<Post, "body">; // body now required
TipoEfecto¿Profundo?
Partial<T>todas las propiedades opcionalesno
Required<T>todas las propiedades obligatorias, sin undefinedno
DeepPartial<T> (propio)opcional en todos los nivelessí
PartialBy<T, K> (propio)solo las claves K opcionalesno
Readonly<T>todas las propiedades readonlyno

Preguntas frecuentes

¿Qué hace Partial en TypeScript?

Partial<T> crea un tipo con todas las propiedades de T marcadas como opcionales. Para interface User { name: string; email: string }, Partial<User> es { name?: string; email?: string }, así que {}, { name: "Ada" } y un usuario completo son todos valores válidos.

¿Partial es profundo en TypeScript?

No, Partial solo afecta a las propiedades del primer nivel. Un objeto anidado dentro de un Partial<T> tiene que seguir estando completo. Para una versión recursiva, escribe un tipo DeepPartial<T> que se aplique a sí mismo sobre las propiedades de tipo objeto.

¿Cuál es el opuesto de Partial en TypeScript?

Required<T>. Quita el ? de todas las propiedades y además quita undefined de sus tipos, así que Required<{ port?: number }> es { port: number }. Se define como un mapped type con el modificador -?.

¿Cómo hago opcionales solo algunas propiedades?

Combina Omit, Pick y Partial: type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>. PartialBy<User, "email"> deja todas las propiedades como estaban, salvo email, que pasa a ser opcional.

¿Por qué una propiedad queda en undefined después de fusionar una actualización Partial?

Partial<T> permite que una propiedad esté presente con el valor undefined, y el spread de objetos la copia: { ...user, ...{ name: undefined } } tiene name: undefined aunque TypeScript tipe el resultado como User. Filtra los valores undefined antes de fusionar, o activa exactOptionalPropertyTypes para que se rechace un undefined explícito.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR