Menu

readonly en TypeScript : propriétés, Readonly<T> et tableaux

Le modificateur readonly et le utility type Readonly<T> empêchent le code de réaffecter des propriétés. Découvrez les propriétés et champs de classe readonly, Readonly<T>, les tableaux readonly (readonly T[] et ReadonlyArray), ReadonlyMap et ReadonlySet, pourquoi readonly est superficiel et limité à la compilation, et comment il se compare à Object.freeze et as const.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

readonly marque une propriété qui peut être définie une fois, à la création de l'objet, et jamais réaffectée. Readonly<T> l'applique à toutes les propriétés d'un type, et readonly T[] fait de même pour les tableaux :

La dernière ligne montre le fait le plus important à propos de readonly : il est vérifié par le compilateur, pas imposé à l'exécution. L'affectation était une erreur de compilation (supprimée ici avec @ts-expect-error), et pourtant le JavaScript émis l'a exécutée. Sans la suppression, le fichier ne compilerait pas, et c'est là que readonly fait son travail.

Propriétés readonly

Placez readonly devant le nom d'une propriété dans une interface, un type littéral ou une classe. La propriété peut être initialisée mais pas réaffectée :

Dans une classe, un champ readonly peut être affecté dans sa déclaration ou dans le constructeur, et nulle part ailleurs. La forme la plus courte est une propriété de paramètre, constructor(readonly id: string) {}, qui déclare et affecte le champ en une seule étape. La page sur les classes couvre les champs et les constructeurs en général.

Readonly<T> : toutes les propriétés d'un coup

Readonly<T> est un utility type qui marque toutes les propriétés de T comme readonly. Il est utile pour des valeurs que l'on fait circuler sans devoir les modifier, comme l'état d'une application :

La signature de la fonction indique au lecteur que addItem renvoie un nouvel état au lieu de modifier l'ancien, et le compilateur oblige la fonction à tenir cette promesse. Readonly<T> est défini comme le mapped type { readonly [P in keyof T]: T[P] }.

Tableaux readonly : readonly T[] et ReadonlyArray<T>

readonly number[] et ReadonlyArray<number> sont le même type. Ils retirent toutes les méthodes qui modifient le tableau (push, pop, shift, splice, sort, reverse, fill...) et interdisent l'affectation par index. Les méthodes qui ne modifient rien restent et renvoient des tableaux ordinaires :

Prendre un readonly T[] en paramètre est une promesse faite aux appelants : vous ne modifierez pas leur tableau. C'est dans l'autre sens que l'on bloque : un tableau readonly ne peut pas être passé à une fonction qui prend un T[] simple, car cette fonction pourrait le modifier.

index.ts(7,17): error TS4104: The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.

La solution est de faire accepter readonly number[] à sum, puisqu'elle ne modifie rien. Une fonction qui ne fait que lire un tableau devrait toujours prendre le type readonly ; elle accepte alors les deux sortes de tableaux. Si la fonction ne vous appartient pas, passez une copie : sum([...prices]).

ReadonlyMap et ReadonlySet

Les Maps et les sets ont aussi des versions readonly. ReadonlyMap<K, V> possède get, has, size, forEach et les itérateurs, mais pas set, delete ni clear ; ReadonlySet<T> n'a ni add, ni delete, ni clear :

Une classe garde souvent une Map privée modifiable et l'expose via un getter typé ReadonlyMap, pour que le code extérieur puisse lire les données sans pouvoir les modifier par cette référence.

readonly est superficiel

readonly et Readonly<T> ne protègent que la propriété elle-même, pas l'objet ou le tableau vers lequel elle pointe :

DeepReadonly<T> s'applique à chaque type objet imbriqué, et comme un mapped type appliqué à un type tableau produit un tableau readonly, members devient readonly string[]. Cela reste une promesse au niveau des types, pas une protection à l'exécution.

Seulement à la compilation : la mutation par une autre référence

Un type readonly contrôle ce qu'une référence a le droit de faire. Une autre référence au même objet, typée sans readonly, peut le modifier, et TypeScript autorise même à affecter un type readonly à un type modifiable :

L'affectation mutable = settings compile parce que TypeScript ne tient pas compte des propriétés readonly quand il vérifie si deux types objets sont compatibles ; le handbook TypeScript le dit explicitement, et précise que des propriétés readonly peuvent donc changer par aliasing. Les tableaux readonly sont différents : l'erreur TS4104 plus haut est précisément cette vérification. Object.freeze empêche vraiment les modifications à l'exécution : le code émis tourne en mode strict, où écrire dans une propriété gelée lève une TypeError. Comme readonly, Object.freeze est superficiel.

readonly, const, as const et Object.freeze

as const sur un littéral rend toutes les propriétés readonly à toutes les profondeurs et conserve les types littéraux, ce qui est souvent le moyen le plus simple d'obtenir une valeur profondément readonly :

const theme = { mode: "dark", sizes: [12, 14] } as const;
// { readonly mode: "dark"; readonly sizes: readonly [12, 14] }
S'applique àProfond ?Effet à l'exécutionExemple
constune liaison de variablenonla variable ne peut pas être réaffectéeconst user = {...}
readonlyune propriété ou un type tableaunonaucunreadonly id: string
Readonly<T>toutes les propriétés d'un typenonaucunReadonly<State>
as constune expression littéraleouiaucun{ ... } as const
Object.freezeune valeur objetnonles écritures échouent (exception en mode strict)Object.freeze(obj)

const et readonly répondent à des questions différentes : const empêche le nom de pointer ailleurs, readonly empêche une propriété de changer. Les propriétés d'un objet const peuvent toujours être réaffectées, sauf si elles sont readonly.

Questions fréquentes

À quoi sert readonly en TypeScript ?

readonly marque une propriété qui peut être définie à la création de l'objet (ou dans le constructeur d'une classe) mais pas réaffectée ensuite. L'affecter plus tard est une erreur de compilation, TS2540. Ce n'est qu'une vérification de types : le JavaScript émis ne contient aucune protection.

Quelle est la différence entre readonly et const en TypeScript ?

const concerne une variable : le nom ne peut pas pointer vers une autre valeur, mais l'objet qu'il contient peut toujours être modifié. readonly concerne une propriété : cette propriété ne peut pas être réaffectée. const user = { name: "Ada" } autorise encore user.name = "x" ; une propriété readonly name non.

Comment rendre un tableau readonly en TypeScript ?

Annotez-le comme readonly T[] ou ReadonlyArray<T> (c'est le même type). Les méthodes qui modifient le tableau, comme push, pop, sort et splice, disparaissent du type, et l'affectation par index est une erreur. Les méthodes qui ne modifient rien, comme map, filter et slice, fonctionnent toujours et renvoient des tableaux ordinaires.

Readonly est-il profond en TypeScript ?

Non. Readonly<T> et readonly ne protègent que les propriétés de premier niveau ; les objets et tableaux imbriqués peuvent toujours être modifiés. Utilisez as const sur un littéral, ou écrivez un type récursif DeepReadonly<T>, pour une protection profonde au niveau des types.

readonly empêche-t-il les modifications à l'exécution ?

Non. Les types sont effacés, donc une propriété readonly est une propriété ordinaire à l'exécution, et du code disposant d'une référence modifiable au même objet (ou du JavaScript pur) peut encore la changer. Utilisez Object.freeze quand vous avez besoin d'une protection à l'exécution ; TypeScript type son résultat comme Readonly<T>.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER