Partial, Required, Readonly : Rendre les propriétés optionnelles, obligatoires ou en lecture seule
Après Pick et Omit, place à trois utilitaires qui permettent de modifier dynamiquement les contraintes des propriétés d'une interface, sans toucher à sa définition d'origine.
Publié le 9 mars 2026 • Temps de lecture : 8 min
Introduction : Pourquoi modifier les contraintes des propriétés ?
Dans notre précédent article sur Pick et Omit, nous avons vu comment créer des sous‑ensembles d'interfaces en sélectionnant ou excluant des propriétés. Aujourd'hui, nous allons explorer une autre famille d'utilitaires : ceux qui transforment les contraintes des propriétés.
Il arrive souvent qu'une même entité doive être utilisée dans des contextes différents :
- Un formulaire de création où tous les champs sont obligatoires.
- Un formulaire d'édition où seuls quelques champs sont modifiables et optionnels.
- Une configuration chargée une fois pour toutes et qui ne doit plus être altérée.
Plutôt que de dupliquer vos interfaces, TypeScript met à disposition trois utilitaires génériques : Partial, Required et Readonly. Ils permettent de transformer un type existant en un nouveau type où chaque propriété devient respectivement optionnelle, obligatoire ou en lecture seule.
Prérequis
Cet article est le troisième volet de notre série sur les utilitaires TypeScript. Si vous n'avez pas lu l'article sur Pick et Omit, pas d'inquiétude : ces outils sont indépendants, mais la philosophie de composition des types reste la même.
1. Partial<Type> : Tout rendre optionnel
Partial<T> construit un type à partir de T où toutes les propriétés deviennent optionnelles (le ? est ajouté à chacune).
Syntaxe
Exemple simple
id: number;
nom: string;
email: string;
motDePasse: string;
}
// Pour une mise à jour partielle, tous les champs deviennent optionnels
type UtilisateurPartiel = Partial<Utilisateur>;
function mettreÀJourUtilisateur(id: number, modifications: UtilisateurPartiel) {
// on peut ne passer que les champs à modifier
console.log("Mise à jour :", modifications);
}
mettreÀJourUtilisateur(1, { nom: "Nouveau nom" }); // ✅
mettreÀJourUtilisateur(1, { email: "new@mail.com", motDePasse: "secret" }); // ✅
mettreÀJourUtilisateur(1, {}); // ✅ (aucune modification)
Cas d'usage concret : formulaire d'édition
Imaginez un composant React qui permet de modifier seulement quelques champs d'un utilisateur. Avec Partial, l'état local du formulaire peut être typé précisément.
nom: string;
bio: string;
avatar: string;
préférences: { theme: "clair" | "sombre"; notifications: boolean };
}
function EditeurProfil() {
const [formData, setFormData] = useState<Partial<Profil>>({});
const handleChange = (champ: keyof Profil, valeur: any) => {
setFormData(prev => ({ ...prev, [champ]: valeur }));
};
// Ne soumet que les champs modifiés
return ( ... );
}
🎯 À retenir
Partial est idéal pour les mises à jour partielles, les formulaires en plusieurs étapes, ou lorsque vous voulez accumuler progressivement des données.
2. Required<Type> : Tout rendre obligatoire
Required<T> est l'opposé de Partial : il construit un type où toutes les propriétés deviennent obligatoires (le ? est retiré).
Syntaxe
Exemple simple
url: string;
timeout?: number;
retries?: number;
}
type ConfigObligatoire = Required<Configuration>;
// { url: string; timeout: number; retries: number; }
function initialiserApp(config: ConfigObligatoire) {
// on est sûr que timeout et retries existent
console.log(`Timeout: ${config.timeout}ms`);
}
// ❌ Erreur : il manque timeout et retries
// initialiserApp({ url: "https://api.example.com" });
// ✅ OK
initialiserApp({ url: "https://api.example.com", timeout: 5000, retries: 3 });
Cas d'usage : forcer la complétude après une phase de construction
Parfois, on construit un objet progressivement (avec des champs optionnels), puis on veut s'assurer qu'il est complet avant de l'utiliser.
hôte?: string;
port?: number;
ssl?: boolean;
}
class Connexion {
private options: OptionsBuilder = {};
setHôte(hôte: string) { this.options.hôte = hôte; }
setPort(port: number) { this.options.port = port; }
setSSL(ssl: boolean) { this.options.ssl = ssl; }
connecter() {
const optsValides = this.options as Required<OptionsBuilder>;
// maintenant hôte, port, ssl sont garantis non-undefined
console.log(`Connexion à ${optsValides.hôte}:${optsValides.port} (SSL: ${optsValides.ssl})`);
}
}
💡 Required est pratique pour valider qu'un objet est complet avant une opération critique, ou pour transformer une interface optionnelle en contrat strict.
3. Readonly<Type> : Tout rendre immutable
Readonly<T> rend toutes les propriétés de T en lecture seule (readonly). Toute tentative de modification après création sera signalée par TypeScript.
Syntaxe
Exemple simple
x: number;
y: number;
}
const point: Readonly<Point> = { x: 10, y: 20 };
// point.x = 15; // ❌ Erreur : lecture seule
Cas d'usage : configuration immuable, états partagés
apiUrl: string;
timeout: number;
features: string[];
}> = {
apiUrl: "https://api.example.com",
timeout: 5000,
features: ["dark-mode", "notifications"]
};
// CONFIG.timeout = 10000; // ❌ lecture seule
// CONFIG.features.push("new"); // ✅ Attention : le tableau est toujours mutable !
⚠️ Readonly n'est pas profond (shallow). Les objets imbriqués ou les tableaux restent mutables si leur propre type ne les protège pas. Pour une immutabilité profonde, il faut combiner avec d'autres techniques ou utiliser Readonly récursivement.
Immutable profond avec récursivité
readonly [P in keyof T]: DeepReadonly<T[P]>;
};
interface État {
utilisateur: { nom: string; âge: number };
todos: string[];
}
const état: DeepReadonly<État> = {
utilisateur: { nom: "Alice", âge: 30 },
todos: ["Apprendre TypeScript"]
};
// état.utilisateur.nom = "Bob"; // ❌ erreur (profond)
// état.todos.push("Nouveau"); // ❌ erreur (push n'existe pas sur ReadonlyArray)
Combinaisons et cas avancés
Ces trois utilitaires peuvent être combinés entre eux ou avec Pick/Omit pour obtenir des types très précis.
Exemple 1 : Formulaire d'édition avec champs obligatoires et lecture seule
id: number;
nom: string;
email: string;
avatar: string;
}
// On permet de modifier nom, email, avatar, mais l'id est en lecture seule
type ProfilÉditable = Partial<Omit<Profil, "id">> & { readonly id: number };
const données: ProfilÉditable = { id: 1, nom: "Jean" }; // ✅
// données.id = 2; // ❌ erreur
Exemple 2 : Configuration par défaut avec surcharge partielle
timeout: 3000,
retries: 2,
url: "localhost"
} as const;
type ConfigFinale = Required<Partial<typeof DEFAUTS>>; // Tous les champs obligatoires
function créerConfig(surcharge?: Partial<typeof DEFAUTS>): ConfigFinale {
return { ...DEFAUTS, ...surcharge }; // maintenant tout est présent
}
Scénarios réels
🔄 Mise à jour d'état dans Redux / Zustand
utilisateur: Utilisateur;
panier: Produit[];
chargement: boolean;
}
type Action =
| { type: 'SET_USER'; payload: Utilisateur }
| { type: 'UPDATE_USER'; payload: Partial<Utilisateur> }
| { type: 'SET_LOADING'; payload: boolean };
function réducteur(état: État, action: Action): État {
switch (action.type) {
case 'UPDATE_USER':
return { ...état, utilisateur: { ...état.utilisateur, ...action.payload } };
// ...
}
}
🔒 Données sensibles en lecture seule
private _comptes: Map<string, number> = new Map();
getCompte(id: string): Readonly<{ solde: number }> {
const solde = this._comptes.get(id) ?? 0;
return { solde }; // l'extérieur ne peut pas modifier le solde
}
}
Conclusion : Des contraintes adaptées à chaque contexte
Avec Partial, Required et Readonly, vous disposez d'outils simples mais puissants pour moduler les exigences de vos types :
- Partial pour les mises à jour progressives ou les formulaires.
- Required pour garantir l'exhaustivité avant une opération critique.
- Readonly pour protéger l'intégrité des données partagées.
🚀 Prochaines étapes
Notre série sur les utilitaires TypeScript se poursuit. Voici les prochains sujets :
- Parameters, ConstructorParameters, ReturnType : Extraire les types des fonctions et constructeurs.
- Awaited : Déballer le type d'une Promise.
- Template Literal Types : Créer des types dynamiques à partir de chaînes.
💡 Le mot de la fin
Partial, Required et Readonly sont les couteaux suisses de la modification des contraintes. Ils vous évitent de multiplier les interfaces pour des variantes qui ne diffèrent que par l'optionalité ou la mutabilité. Utilisez‑les sans modération !
👋 Un mot de l'auteur
Merci d'avoir lu ce troisième article de la série ! J'espère que ces explications sur Partial, Required et Readonly vous aideront à écrire du code TypeScript plus expressif et plus sûr.
Si ce contenu vous a été utile, n'hésitez pas à le partager ou à me laisser un commentaire. Votre soutien est ce qui me motive à continuer.
📢 Prochain article : "Parameters, ReturnType et Awaited – Travailler avec les types de fonctions." Restez connectés !
Questions Fréquentes
Partial et Required fonctionnent-ils sur les propriétés optionnelles d'origine ?
Oui. Partial rendra optionnelles même les propriétés qui étaient obligatoires ; Required rendra obligatoires même celles qui étaient optionnelles.
Que se passe-t-il si j'applique Required à un type déjà totalement requis ?
Le type résultant est identique à l'original. Required<T> est idempotent sur les types déjà requis.
Readonly est-il récursif ?
Non, Readonly est superficiel. Seules les propriétés de premier niveau deviennent en lecture seule. Pour une immutabilité profonde, il faut définir un type récursif (comme montré plus haut).
Peut-on utiliser ces utilitaires avec des classes ?
Absolument. Ils fonctionnent sur la forme (shape) des types, donc sur les classes également.
Comment rendre seulement quelques propriétés optionnelles, sans utiliser Partial sur tout l'objet ?
Vous pouvez combiner Pick et Partial avec une intersection. Par exemple : type Modif = Partial<Pick<T, 'a' | 'b'>> & Omit<T, 'a' | 'b'>;