Partial<T> est un utility type intégré qui rend optionnelles toutes les propriétés de T. C'est le type naturel d'une mise à jour ou d'un patch : l'appelant n'envoie que les champs qui ont changé.
Partial<User> vaut { id?: number; name?: string; email?: string }. Le compilateur vérifie toujours les champs que vous passez : { nmae: "x" } ou { name: 42 } est une erreur, et c'est ce qui rend Partial meilleur qu'un paramètre object ou any trop permissif.
Comment Partial est défini
Partial est un mapped type d'une ligne dans la bibliothèque standard de TypeScript :
type Partial<T> = {
[P in keyof T]?: T[P];
};
Pour chaque clé P de T, il déclare une propriété optionnelle du même type. Comme il parcourt keyof T, il conserve readonly sur les propriétés qui l'avaient. Étant un type ordinaire, il n'a aucun effet à l'exécution : l'objet passé comme changes est le même dans tous les cas.
Lire un Partial donne T | undefined
Chaque propriété d'un Partial<T> peut être absente, donc en lire une donne le type de la propriété plus undefined. Le compilateur vous oblige à gérer le cas de l'absence :
{ ...defaults, ...opts } est la façon habituelle de retransformer un Partial<Options> en Options complet : étalez d'abord les valeurs par défaut et laissez les valeurs fournies les remplacer.
Le piège du undefined explicite
Une propriété optionnelle peut être absente, mais elle peut aussi être présente avec la valeur undefined. Le spread d'objet copie ce undefined par-dessus la vraie valeur, et le type du résultat ne le montre pas :
Ce piège se déclenche quand un patch est construit à partir d'un formulaire ou d'une query string où les champs vides deviennent undefined. L'option de compilation exactOptionalPropertyTypes (qui ne fait pas partie de strict) fait de { name: undefined } une erreur de compilation pour name?: string, sauf si vous écrivez name?: string | undefined, ce qui règle le problème à la source.
Partial est superficiel
Partial ne rend optionnelles que les propriétés de premier niveau. Un objet imbriqué, si vous le passez, doit être complet :
index.ts(8,3): error TS2741: Property 'tabSize' is missing in type '{ fontSize: number; }' but required in type '{ fontSize: number; tabSize: number; }'.
La propriété editor est optionnelle, mais une fois présente elle a le type { fontSize: number; tabSize: number }, inchangé. Pour des objets de paramètres, des patchs d'API et des fixtures de test, on veut souvent des propriétés optionnelles à tous les niveaux. Il faut pour cela un type récursif.
DeepPartial : un Partial récursif
TypeScript n'a pas de version profonde intégrée, mais elle tient en quelques lignes. Les fonctions et les tableaux sont laissés tels quels, car rendre optionnels les éléments d'un tableau autoriserait [undefined] :
Le type est récursif ; la fusion ne l'est pas. applySettings fusionne editor à la main, car le spread d'objet est lui aussi superficiel. Des fonctions génériques de fusion profonde existent dans des bibliothèques comme lodash (merge), et leur typage est plus difficile que le type ci-dessus.
Required : l'inverse de Partial
Required<T> retire le ? de chaque propriété. Il est défini avec le modificateur -?, qui retire aussi undefined du type de chaque propriété :
Le schéma est le même qu'avec Partial, inversé : les utilisateurs d'une API passent une configuration souple, et le code interne travaille avec une version Required où chaque valeur est assurée d'exister. Le spread a le même trou que la fonction de mise à jour plus haut : un appelant qui passe port: undefined explicitement écrase la valeur par défaut avec undefined, et le compilateur l'accepte sauf si exactOptionalPropertyTypes est activé. Required est superficiel de la même façon que Partial.
Rendre seulement certaines propriétés optionnelles ou obligatoires
Partial et Required s'appliquent à toutes les propriétés. Pour n'en changer que quelques-unes, découpez le type avec Pick et Omit puis recomposez-le :
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
| Type | Effet | Profond ? |
|---|---|---|
Partial<T> | toutes les propriétés optionnelles | non |
Required<T> | toutes les propriétés obligatoires, undefined retiré | non |
DeepPartial<T> (le vôtre) | optionnel à tous les niveaux | oui |
PartialBy<T, K> (le vôtre) | seules les clés K optionnelles | non |
Readonly<T> | toutes les propriétés readonly | non |
Questions fréquentes
À quoi sert Partial en TypeScript ?
Partial<T> crée un type avec toutes les propriétés de T marquées comme optionnelles. Pour interface User { name: string; email: string }, Partial<User> vaut { name?: string; email?: string }, donc {}, { name: "Ada" } et un utilisateur complet sont tous des valeurs valides.
Partial est-il profond en TypeScript ?
Non, Partial n'agit que sur les propriétés de premier niveau. Un objet imbriqué dans un Partial<T> doit toujours être complet. Pour une version récursive, écrivez un type DeepPartial<T> qui s'applique lui-même aux propriétés de type objet.
Quel est l'inverse de Partial en TypeScript ?
Required<T>. Il retire le ? de chaque propriété et retire aussi undefined de leurs types, donc Required<{ port?: number }> vaut { port: number }. Il est défini comme un mapped type avec le modificateur -?.
Comment rendre optionnelles seulement certaines propriétés ?
Combinez Omit, Pick et Partial : type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>. PartialBy<User, "email"> garde chaque propriété telle quelle, sauf email, qui devient optionnelle.
Pourquoi une propriété vaut-elle undefined après la fusion d'une mise à jour Partial ?
Partial<T> autorise une propriété à être présente avec la valeur undefined, et le spread d'objet la copie : { ...user, ...{ name: undefined } } a name: undefined alors que TypeScript type le résultat comme User. Filtrez les valeurs undefined avant la fusion, ou activez exactOptionalPropertyTypes pour qu'un undefined explicite soit refusé.