Menu

readonly en TypeScript: propiedades, Readonly<T> y arrays

El modificador readonly y el utility type Readonly<T> impiden que el código reasigne propiedades. Aprende las propiedades readonly y los campos de clase, Readonly<T>, los arrays readonly (readonly T[] y ReadonlyArray), ReadonlyMap y ReadonlySet, por qué readonly es superficial y solo de compilación, y cómo se compara con Object.freeze y as const.

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

readonly marca una propiedad que se puede asignar una vez, al crear el objeto, y nunca reasignar. Readonly<T> lo aplica a todas las propiedades de un tipo, y readonly T[] hace lo mismo con los arrays:

La última línea muestra lo más importante de readonly: lo comprueba el compilador, no se hace cumplir en ejecución. La asignación era un error de compilación (aquí suprimido con @ts-expect-error), y aun así el JavaScript generado la ejecutó. Sin la supresión, el archivo no compilaría, y ahí es donde readonly hace su trabajo.

Propiedades readonly

Pon readonly delante del nombre de una propiedad en una interfaz, en un tipo literal o en una clase. La propiedad se puede inicializar pero no reasignar:

En una clase, un campo readonly se puede asignar en su declaración o en el constructor, y en ningún otro sitio. La forma más corta es una parameter property, constructor(readonly id: string) {}, que declara y asigna el campo en un solo paso. La página de clases trata los campos y los constructores en general.

Readonly<T>: todas las propiedades a la vez

Readonly<T> es un utility type que marca todas las propiedades de T como readonly. Es útil para valores que pasas de un sitio a otro pero que no se deben cambiar, como el estado de una aplicación:

La firma de la función le dice a quien la lee que addItem devuelve un estado nuevo en lugar de cambiar el anterior, y el compilador obliga a la función a cumplirlo. Readonly<T> se define como el mapped type { readonly [P in keyof T]: T[P] }.

Arrays readonly: readonly T[] y ReadonlyArray<T>

readonly number[] y ReadonlyArray<number> son el mismo tipo. Quitan todos los métodos que modifican (push, pop, shift, splice, sort, reverse, fill...) y prohíben la asignación por índice. Los métodos que no modifican se mantienen y devuelven arrays normales:

Recibir readonly T[] como parámetro es una promesa a quien llama de que no modificarás su array. La otra dirección es donde la gente se atasca: un array readonly no se puede pasar a una función que recibe un T[] normal, porque esa función podría modificarlo.

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

La solución es cambiar sum para que acepte readonly number[], ya que no modifica nada. Las funciones que solo leen un array deberían recibir siempre el tipo readonly; así aceptan los dos tipos de array. Si la función no es tuya, pásale una copia: sum([...prices]).

ReadonlyMap y ReadonlySet

Los Maps y los sets también tienen versiones readonly. ReadonlyMap<K, V> tiene get, has, size, forEach y los iteradores, pero no set, delete ni clear; ReadonlySet<T> no tiene add, delete ni clear:

Es habitual que una clase guarde un Map privado modificable y lo exponga mediante un getter tipado como ReadonlyMap, para que el código de fuera pueda leer los datos pero no cambiarlos a través de esa referencia.

readonly es superficial

readonly y Readonly<T> solo protegen la propiedad en sí, no el objeto o el array al que apunta:

DeepReadonly<T> se aplica a sí mismo sobre cada tipo objeto anidado y, como un mapped type sobre un tipo array produce un array readonly, members pasa a ser readonly string[]. Sigue siendo una promesa a nivel de tipos, no una protección en ejecución.

Solo en compilación: cambios a través de otra referencia

Un tipo readonly controla lo que puede hacer una referencia. Otra referencia al mismo objeto, tipada sin readonly, puede cambiarlo, y TypeScript incluso permite asignar un tipo readonly a uno modificable:

La asignación mutable = settings compila porque TypeScript no tiene en cuenta las propiedades readonly al comprobar si dos tipos objeto son compatibles; el handbook de TypeScript lo dice directamente, y señala que por eso las propiedades readonly pueden cambiar a través de alias. Con los arrays readonly es distinto: el error TS4104 de antes es justamente esa comprobación. Object.freeze sí impide cambios en ejecución: el código generado se ejecuta en modo estricto, donde escribir en una propiedad congelada lanza un TypeError. Igual que readonly, Object.freeze es superficial.

readonly, const, as const y Object.freeze

as const sobre un literal hace readonly todas las propiedades a cualquier profundidad y conserva los tipos literales, y a menudo es la forma más fácil de conseguir un valor readonly en profundidad:

const theme = { mode: "dark", sizes: [12, 14] } as const;
// { readonly mode: "dark"; readonly sizes: readonly [12, 14] }
Se aplica a¿Profundo?Efecto en ejecuciónEjemplo
constla asignación de una variablenola variable no se puede reasignarconst user = {...}
readonlyuna propiedad o un tipo arraynoningunoreadonly id: string
Readonly<T>todas las propiedades de un tipononingunoReadonly<State>
as constuna expresión literalsíninguno{ ... } as const
Object.freezeun valor objetonolas escrituras fallan (lanzan un error en modo estricto)Object.freeze(obj)

const y readonly responden a preguntas distintas: const impide que el nombre apunte a otro sitio, readonly impide que una propiedad cambie. Las propiedades de un objeto const se pueden seguir reasignando salvo que sean readonly.

Preguntas frecuentes

¿Qué hace readonly en TypeScript?

readonly marca una propiedad que se puede asignar al crear el objeto (o en el constructor de una clase) pero no reasignar después. Asignarle un valor más tarde es un error de compilación, TS2540. Es solo una comprobación de tipos: el JavaScript generado no contiene ninguna protección.

¿Qué diferencia hay entre readonly y const en TypeScript?

const tiene que ver con una variable: el nombre no puede apuntar a otro valor, pero el objeto que contiene se puede seguir cambiando. readonly tiene que ver con una propiedad: esa propiedad no se puede reasignar. const user = { name: "Ada" } sigue permitiendo user.name = "x"; una propiedad readonly name no.

¿Cómo hago un array readonly en TypeScript?

Anótalo como readonly T[] o ReadonlyArray<T> (es el mismo tipo). Los métodos que modifican, como push, pop, sort y splice, desaparecen del tipo, y asignar por índice es un error. Los métodos que no modifican, como map, filter y slice, siguen funcionando y devuelven arrays normales.

¿Readonly es profundo en TypeScript?

No. Readonly<T> y readonly solo protegen las propiedades del primer nivel; los objetos y arrays anidados se pueden seguir cambiando. Usa as const sobre un literal, o escribe un tipo recursivo DeepReadonly<T>, para tener protección profunda a nivel de tipos.

¿readonly impide cambios en tiempo de ejecución?

No. Los tipos se borran, así que en ejecución una propiedad readonly es una propiedad normal, y el código con una referencia modificable al mismo objeto (o JavaScript normal) puede cambiarla. Usa Object.freeze cuando necesites protección en ejecución; TypeScript tipa su resultado como Readonly<T>.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR