Перейти к содержимому
Теория · Объектные типы · урок 6 из 7

Свойства только для чтения

Не все поля анкеты заполняете вы. Номер анкеты отдел кадров печатает заранее, ещё в типографии. Остальные поля вписывают ручкой, а напечатанный номер исправить нельзя: по нему анкету находят в архиве.

В JavaScript любое свойство объекта похоже на поле для ручки: его можно перезаписать. Иногда это опасно. Если у пользователя поменяется id, все записи, которые ссылаются на этот id, укажут на кого-то другого.

Модификатор readonly перед свойством делает его свойством только для чтения: читать его можно, а записывать после создания объекта — нет.

TypeScript
type User = {
    readonly id: number;
    name: string;
};

const user: User = { id: 1, name: "Ann" };
user.name = "Anna";
console.log(user.id, user.name); // 1 Anna

Разберём пример по строкам:

  1. readonly id: number — свойство id только для чтения.
  2. Литерал { id: 1, name: "Ann" } задаёт id при создании объекта, и это разрешено: номер печатают один раз, когда выдают бланк.
  3. user.name = "Anna" меняет обычное свойство, и компилятор не возражает.
  4. Программа печатает 1 Anna.

Помечайте readonly то, что не должно меняться после создания объекта: номера, даты создания, исходные настройки.

Запись в readonly-свойство

Давайте попробуем поменять номер после создания объекта:

TypeScript
type User = {
    readonly id: number;
    name: string;
};

const user: User = { id: 1, name: "Ann" };
user.id = 2; // ✗
TEXT
error TS2540: Cannot assign to 'id' because it is a read-only property.

Компилятор отвечает: «нельзя присвоить id, потому что это свойство только для чтения». Ошибка стоит на строке записи, а не в объявлении типа: компилятор показывает место, где правило нарушили.

readonly и const защищают разное:

  • const user запрещает положить в переменную другой объект, но менять свойства объекта не мешает;
  • readonly id защищает одно свойство, в какой бы переменной ни лежал объект — хоть в const, хоть в let.

Проверяет только tsc

readonly — часть типа, а типы стираются. Компилятор tsc сообщит об ошибке в примере выше, но JavaScript-файл всё равно запишет, и слова readonly в нём не будет. Если этот JavaScript запустить, присваивание user.id = 2 сработает, и id станет 2. То же случится при запуске через node file.ts: он стирает типы, не проверяя их.

Здесь аналогия с напечатанным полем перестаёт работать. Типографскую краску не сотрёшь, а readonly действует, только пока код проверяет компилятор.

На собеседованиях любят спрашивать, чем readonly отличается от Object.freeze. Ответ: readonly проверяет tsc до запуска, а функция Object.freeze из JavaScript запрещает запись в объект во время работы программы.

Защита в один уровень

В свойстве может лежать другой объект — как вкладыш с адресом внутри анкеты. Проверим, защищает ли readonly его поля:

TypeScript
type User = {
    readonly id: number;
    readonly address: { city: string };
};

const user: User = { id: 1, address: { city: "Oslo" } };
user.address.city = "Rome";
console.log(user.address.city); // Rome

Давайте посмотрим, что здесь защищено, а что нет:

  1. readonly address запрещает положить в address другой объект: user.address = { city: "Rome" } компилятор не пропустит, ошибка та же — TS2540.
  2. Свойство city внутри адреса — обычное, поэтому user.address.city = "Rome" проходит.
  3. Программа печатает Rome.

readonly защищает само свойство, но не объект, который в нём лежит. Чтобы закрыть и город, пометьте его тоже: address: { readonly city: string }.

Частая ошибка

readonly поставили перед переменной, чтобы её нельзя было менять:

TypeScript
readonly let total = 5; // ✗
console.log(total);
TEXT
error TS1024: 'readonly' modifier can only appear on a property declaration or index signature.

Компилятор отвечает, что readonly ставят только перед свойством в объявлении типа. Для переменной, которую нельзя переприсвоить, в JavaScript уже есть const: const total = 5;.

Главное

  • readonly перед свойством разрешает задать его при создании объекта и запрещает запись потом: ошибка TS2540.
  • Проверяет readonly только tsc: в JavaScript его нет, и при запуске запись сработает.
  • readonly защищает один уровень: свойства вложенного объекта остаются изменяемыми.
  • const защищает переменную, а readonly — свойство.