Avatar ·

Что такое get и set (геттеры и сеттеры) в TypeScript и как их правильно использовать?

Всем привет! Я только начал изучать TypeScript после JavaScript, и у меня возникла путаница с геттерами и сеттерами. В JS я привык делать свойства объектов публичными и обращаться к ним напрямую: `user.name = 'John'`. Но в TypeScript я вижу, что многие используют `get` и `set` для доступа к свойствам. Я не совсем понимаю, зачем это нужно и как правильно это реализовать. Вот пример моего кода, где я пытаюсь сделать класс User с геттером и сеттером: ```typescript class User { private name: string; constructor(name: string) { this.name = name; } get name(): string { return this.name; } set name(value: string) { this.name = value; } } const user = new User('Alice'); console.log(user.name); ``` Но TypeScript ругается на такое определение. Я получаю ошибку: "Property 'name' is already declared in the upper scope". И ещё я не понимаю, зачем вообще нужны геттеры и сеттеры, если можно просто сделать `public name: string` и обращаться к нему напрямую? Пожалуйста, объясните на простых примерах: 1. Как правильно объявлять геттеры и сеттеры в TypeScript? 2. Какие практические преимущества они дают? 3. Когда их стоит использовать, а когда можно обойтись публичными полями? Заранее спасибо за помощь! <<>>

Правильное объявление геттеров и сеттеров

Основная проблема вашего кода в том, что вы назвали приватное поле и геттер/сеттер одинаково — name. В TypeScript это вызывает конфликт. Вам нужно использовать разные имена. Обычно приватное поле называют с нижним подчеркиванием в начале:

class User {
  private _name: string;

  constructor(name: string) {
    this._name = name;
  }

  get name(): string {
    return this._name;
  }

  set name(value: string) {
    if (value.length > 0) {
      this._name = value;
    } else {
      console.error('Имя не может быть пустым');
    }
  }
}

const user = new User('Alice');
console.log(user.name); // Alice
user.name = 'Bob';
console.log(user.name); // Bob
user.name = ''; // Имя не может быть пустым

Зачем нужны геттеры и сеттеры?

Главное преимущество — контроль доступа и валидация. Вот несколько практических сценариев:

  • Валидация данных — как в примере выше, можно проверять, что имя не пустое
  • Вычисляемые свойства — геттер может возвращать производное значение
  • Логирование — можно логировать каждое обращение к свойству
  • Ленивая инициализация — вычислять значение только при первом обращении
  • Совместимость с API — можно менять внутреннюю структуру, не ломая внешний интерфейс

Практический пример с вычисляемым свойством

class Rectangle {
  private _width: number;
  private _height: number;

  constructor(width: number, height: number) {
    this._width = width;
    this._height = height;
  }

  get width(): number {
    return this._width;
  }

  set width(value: number) {
    if (value > 0) {
      this._width = value;
    }
  }

  get height(): number {
    return this._height;
  }

  set height(value: number) {
    if (value > 0) {
      this._height = value;
    }
  }

  // Вычисляемое свойство — не хранится, а вычисляется на лету
  get area(): number {
    return this._width * this._height;
  }
}

const rect = new Rectangle(10, 20);
console.log(rect.area); // 200
rect.width = 15;
console.log(rect.area); // 300

Когда использовать геттеры/сеттеры, а когда публичные поля?

Используйте геттеры/сеттеры, если:

  • Нужна валидация при установке значения
  • Свойство должно быть вычисляемым (например, fullName из firstName и lastName)
  • Вы планируете изменить внутреннюю реализацию в будущем
  • Нужна дополнительная логика (логирование, триггеры событий)

Используйте публичные поля, если:

  • Свойство простое и не требует валидации
  • Это DTO (Data Transfer Object) — простой контейнер данных
  • Производительность критична (геттеры/сеттеры имеют небольшой оверхед)

Современный синтаксис с public и readonly

class User {
  // Простое публичное поле — без геттеров/сеттеров
  public readonly createdAt: Date = new Date();
  
  // Приватное поле с геттером/сеттером
  private _age: number;

  constructor(private _name: string, age: number) {
    this._age = age;
  }

  get name(): string {
    return this._name;
  }

  set name(value: string) {
    if (value.length > 0) {
      this._name = value;
    }
  }

  get age(): number {
    return this._age;
  }
}

Главное правило: геттеры и сеттеры — это инструмент для инкапсуляции. Они позволяют контролировать доступ к внутренним данным и добавлять логику без изменения внешнего API класса.

<<>>

Альтернативный подход: использование Proxy

В некоторых случаях, особенно при работе с динамическими объектами, можно использовать Proxy вместо геттеров/сеттеров в классе. Это позволяет перехватывать обращения к любым свойствам объекта:

function createValidatedObject(target: Record<string, any>) {
  return new Proxy(target, {
    set(obj, prop, value) {
      if (prop === 'name' && typeof value !== 'string') {
        throw new Error('Имя должно быть строкой');
      }
      if (prop === 'age' && (typeof value !== 'number' || value < 0)) {
        throw new Error('Возраст должен быть положительным числом');
      }
      obj[prop] = value;
      return true;
    },
    get(obj, prop) {
      console.log(`Доступ к свойству ${String(prop)}`);
      return obj[prop];
    }
  });
}

const user = createValidatedObject({ name: 'Alice', age: 25 });
console.log(user.name); // Лог: Доступ к свойству name
user.age = 30; // OK
// user.age = -5; // Ошибка: Возраст должен быть положительным числом

Когда это полезно:

  • Когда у вас много свойств и не хочется писать геттер/сеттер для каждого
  • Когда структура объекта динамическая (свойства добавляются на лету)
  • Когда нужна универсальная логика для всех свойств

Недостатки:

  • Нет строгой типизации — TypeScript не может проверить типы через Proxy
  • Производительность немного ниже, чем у нативных геттеров/сеттеров
  • Сложнее отлаживать

В большинстве случаев для классов лучше использовать стандартные геттеры/сеттеры TypeScript — они более производительны, типизированы и понятны другим разработчикам. Proxy стоит применять только в специфических задачах, например, при создании ORM или валидаторов.