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 a | Profundo? | Efeito em tempo de execução | Exemplo | |
|---|---|---|---|---|
const | um binding de variável | não | a variável não pode ser reatribuída | const user = {...} |
readonly | uma propriedade ou um tipo array | não | nenhum | readonly id: string |
Readonly<T> | toda propriedade de um tipo | não | nenhum | Readonly<State> |
as const | uma expressão literal | sim | nenhum | { ... } as const |
Object.freeze | um valor objeto | não | escritas 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>.