Menu

readonly em TypeScript: propriedades, Readonly<T> e arrays

O modificador readonly e o utility type Readonly<T> impedem que o código reatribua propriedades. Veja propriedades readonly e campos de classe, Readonly<T>, arrays readonly (readonly T[] e ReadonlyArray), ReadonlyMap e ReadonlySet, por que readonly é raso e só de tempo de compilação, e como ele se compara com Object.freeze e as const.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

readonly marca uma propriedade que pode ser definida uma vez, quando o objeto é criado, e nunca reatribuída. Readonly<T> aplica isso a toda propriedade de um tipo, e readonly T[] faz o mesmo para arrays:

A última linha mostra o fato mais importante sobre readonly: ele é verificado pelo compilador, não imposto em tempo de execução. A atribuição foi um erro de compilação (suprimido aqui com @ts-expect-error), mas o JavaScript gerado a executou mesmo assim. Sem a supressão, o arquivo não compilaria, e é aí que readonly faz o seu trabalho.

Propriedades readonly

Coloque readonly antes do nome de uma propriedade em uma interface, um type literal ou uma classe. A propriedade pode ser inicializada, mas não reatribuída:

Em uma classe, um campo readonly pode ser atribuído na declaração ou no construtor, e em nenhum outro lugar. A forma mais curta é uma parameter property, constructor(readonly id: string) {}, que declara e atribui o campo em um passo só. A página de classes cobre campos e construtores em geral.

Readonly<T>: todas as propriedades de uma vez

Readonly<T> é um utility type que marca toda propriedade de T como readonly. Ele é útil para valores que você repassa mas que não devem mudar, como o estado de uma aplicação:

A assinatura da função diz a quem lê que addItem retorna um estado novo em vez de alterar o antigo, e o compilador cobra isso da função. Readonly<T> é definido como o mapped type { readonly [P in keyof T]: T[P] }.

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

readonly number[] e ReadonlyArray<number> são o mesmo tipo. Eles removem todo método que modifica (push, pop, shift, splice, sort, reverse, fill...) e proíbem a atribuição por índice. Os métodos que não modificam continuam lá e retornam arrays comuns:

Receber readonly T[] como parâmetro é uma promessa para quem chama de que você não vai modificar o array dele. A outra direção é onde as pessoas travam: um array readonly não pode ser passado para uma função que recebe um T[] comum, porque essa função poderia modificá-lo.

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

A correção é mudar sum para aceitar readonly number[], já que ela não modifica nada. Funções que só leem um array devem sempre receber o tipo readonly; assim elas aceitam os dois tipos de array. Se a função não é sua, passe uma cópia: sum([...prices]).

ReadonlyMap e ReadonlySet

Maps e sets também têm versões readonly. ReadonlyMap<K, V> tem get, has, size, forEach e os iteradores, mas não tem set, delete nem clear; ReadonlySet<T> não tem add, delete nem clear:

Uma classe muitas vezes guarda um Map mutável privado e o expõe por um getter com tipo ReadonlyMap, para que o código de fora possa ler os dados mas não alterá-los por essa referência.

readonly é raso

readonly e Readonly<T> protegem só a propriedade em si, não o objeto ou o array para o qual ela aponta:

DeepReadonly<T> se aplica a todo tipo de objeto aninhado, e como um mapped type sobre um tipo array produz um array readonly, members vira readonly string[]. Continua sendo uma promessa no nível dos tipos, não proteção em tempo de execução.

Só em tempo de compilação: mutação por outra referência

Um tipo readonly controla o que uma referência pode fazer. Outra referência ao mesmo objeto, tipada sem readonly, pode alterá-lo, e o TypeScript até permite atribuir um tipo readonly a um mutável:

A atribuição mutable = settings compila porque o TypeScript não leva em conta propriedades readonly quando verifica se dois tipos de objeto são compatíveis; o handbook do TypeScript diz isso diretamente e observa que, por isso, propriedades readonly podem mudar por aliasing. Arrays readonly são diferentes: o erro TS4104 acima é exatamente essa verificação. Object.freeze realmente impede mudanças em tempo de execução: o código gerado roda em strict mode, onde escrever em uma propriedade congelada lança um TypeError. Assim como readonly, Object.freeze é raso.

readonly, const, as const e Object.freeze

as const em um literal torna toda propriedade readonly em todos os níveis e mantém os literal types, o que muitas vezes é o jeito mais fácil de obter um valor profundamente readonly:

const theme = { mode: "dark", sizes: [12, 14] } as const;
// { readonly mode: "dark"; readonly sizes: readonly [12, 14] }
Aplica-se aProfundo?Efeito em tempo de execuçãoExemplo
constum binding de variávelnãoa variável não pode ser reatribuídaconst user = {...}
readonlyuma propriedade ou um tipo arraynãonenhumreadonly id: string
Readonly<T>toda propriedade de um tiponãonenhumReadonly<State>
as constuma expressão literalsimnenhum{ ... } as const
Object.freezeum valor objetonãoescritas falham (lançam erro em strict mode)Object.freeze(obj)

const e readonly respondem a perguntas diferentes: const impede que o nome aponte para outro lugar, readonly impede que uma propriedade mude. As propriedades de um objeto const ainda podem ser reatribuídas, a menos que sejam readonly.

Perguntas frequentes

O que readonly faz no TypeScript?

readonly marca uma propriedade que pode ser definida quando o objeto é criado (ou no construtor de uma classe), mas não pode ser reatribuída depois. Atribuir a ela mais tarde é um erro de compilação, TS2540. É só uma verificação de tipos: o JavaScript gerado não contém proteção nenhuma.

Qual a diferença entre readonly e const no TypeScript?

const diz respeito a uma variável: o nome não pode apontar para outro valor, mas o objeto que ele guarda ainda pode ser alterado. readonly diz respeito a uma propriedade: essa propriedade não pode ser reatribuída. const user = { name: "Ada" } ainda permite user.name = "x"; uma propriedade readonly name não permite.

Como tornar um array readonly no TypeScript?

Anote-o como readonly T[] ou ReadonlyArray<T> (o mesmo tipo). Métodos que modificam, como push, pop, sort e splice, somem do tipo, e atribuir por índice é erro. Métodos que não modificam, como map, filter e slice, continuam funcionando e retornam arrays comuns.

Readonly é profundo no TypeScript?

Não. Readonly<T> e readonly só protegem as propriedades do primeiro nível; objetos e arrays aninhados ainda podem ser alterados. Use as const em um literal, ou escreva um tipo recursivo DeepReadonly<T>, para ter proteção profunda no nível dos tipos.

readonly impede mudanças em tempo de execução?

Não. Os tipos são apagados, então uma propriedade readonly é uma propriedade comum em tempo de execução, e código com uma referência mutável ao mesmo objeto (ou JavaScript puro) ainda pode alterá-la. Use Object.freeze quando precisar de proteção em tempo de execução; o TypeScript tipa o resultado dele como Readonly<T>.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR