Avatar ·

Геттеры и сеттеры в TypeScript: как правильно использовать get и set?

Всем привет! Я только начал изучать TypeScript и перешел с JavaScript. Вроде бы понимаю классы, но никак не могу разобраться с геттерами и сеттерами (get и set). У меня есть простой класс User, где я хочу хранить имя и фамилию, а при обращении к полю fullName получать их вместе. Я написал такой код: ```typescript class User { firstName: string; lastName: string; get fullName(): string { return `${this.firstName} ${this.lastName}`; } } const user = new User(); user.firstName = "Иван"; user.lastName = "Петров"; console.log(user.fullName); // "Иван Петров" - работает! ``` Но потом я попытался сделать сеттер для fullName, чтобы можно было установить имя и фамилию одной строкой: ```typescript class User { firstName: string = ''; lastName: string = ''; get fullName(): string { return `${this.firstName} ${this.lastName}`; } set fullName(value: string) { const parts = value.split(' '); this.firstName = parts[0]; this.lastName = parts[1]; } } const user = new User(); user.fullName = "Иван Петров"; console.log(user.firstName); // "Иван" console.log(user.lastName); // "Петров" ``` Вроде все работает, но я заметил несколько странностей: 1. Если я напишу `user.fullName = "Иван"` (без фамилии), то `lastName` станет `undefined` - это нормально? 2. Почему в сеттере нельзя использовать `this.fullName = value` - это вызывает бесконечную рекурсию? 3. Есть ли разница между обычным методом `getFullName()` и геттером? Объясните пожалуйста на примерах, как правильно использовать get и set, какие есть подводные камни и best practices. Заранее спасибо! <<>>

Что такое геттеры и сеттеры в TypeScript?

Геттеры (get) и сеттеры (set) — это специальные методы класса, которые позволяют управлять доступом к свойствам объекта. Они выглядят как обычные свойства при использовании, но внутри могут содержать любую логику. Это часть концепции "accessors" (аксессоров) в TypeScript и JavaScript (ES5+).

Основные правила работы с get и set

  1. Геттер обязан возвращать значение — это функция, которая вызывается при чтении свойства.
  2. Сеттер принимает ровно один параметр — значение, которое присваивается свойству.
  3. Геттер и сеттер должны иметь одинаковое имя — иначе TypeScript не свяжет их в одно свойство.
  4. Нельзя использовать рекурсивные вызовы — внутри сеттера нельзя писать this.fullName = value, так как это вызовет тот же сеттер снова и приведет к бесконечной рекурсии (Stack Overflow).

Разбор ваших проблем

Проблема 1: undefined при неполных данных

Да, это нормально, потому что ваш код просто берет первый и второй элементы массива. Если в строке только одно слово, то parts[1] будет undefined. Чтобы избежать этого, нужно добавить валидацию:

set fullName(value: string) {
  const parts = value.split(' ');
  if (parts.length < 2) {
    throw new Error('Имя должно содержать имя и фамилию');
  }
  this.firstName = parts[0];
  this.lastName = parts.slice(1).join(' '); // на случай если фамилия составная
}

Проблема 2: рекурсия в сеттере

Если внутри сеттера написать this.fullName = value, то это снова вызовет сеттер, который снова вызовет себя — и так до бесконечности. Используйте приватные поля для хранения данных:

class User {
  private _firstName: string = '';
  private _lastName: string = '';

  get firstName(): string {
    return this._firstName;
  }

  set firstName(value: string) {
    if (!value.trim()) {
      throw new Error('Имя не может быть пустым');
    }
    this._firstName = value;
  }

  get fullName(): string {
    return `${this._firstName} ${this._lastName}`;
  }

  set fullName(value: string) {
    const parts = value.split(' ');
    this._firstName = parts[0];
    this._lastName = parts.slice(1).join(' ');
  }
}

Проблема 3: геттер vs обычный метод

Разница есть, и она важна:

  • Геттер — вызывается как свойство: user.fullName. Не требует круглых скобок.
  • Метод — вызывается как функция: user.getFullName(). Требует круглых скобок.
  • Геттер не может принимать параметры — для этого нужен метод.
  • Геттер нельзя использовать с оператором присваивания — если нет сеттера, то user.fullName = "..." вызовет ошибку.

Best Practices для get и set

  1. Используйте геттеры для вычисляемых свойств — например, полное имя, возраст из даты рождения, форматированные данные.
  2. Используйте сеттеры для валидации — проверяйте данные перед сохранением.
  3. Не злоупотребляйте — если логика сложная, лучше использовать обычные методы.
  4. Приватные поля с подчеркиванием — это конвенция (_field), чтобы не путать с публичными аксессорами.
  5. TypeScript поддерживает readonly — если нужно только читать, используйте только геттер без сеттера.

Полный пример с валидацией

class User {
  private _firstName: string = '';
  private _lastName: string = '';
  private _age: number = 0;

  get firstName(): string {
    return this._firstName;
  }

  set firstName(value: string) {
    if (value.length < 2) {
      throw new Error('Имя должно содержать минимум 2 символа');
    }
    this._firstName = value;
  }

  get lastName(): string {
    return this._lastName;
  }

  set lastName(value: string) {
    if (value.length < 2) {
      throw new Error('Фамилия должна содержать минимум 2 символа');
    }
    this._lastName = value;
  }

  get fullName(): string {
    return `${this._firstName} ${this._lastName}`;
  }

  set fullName(value: string) {
    const parts = value.trim().split(/\s+/);
    if (parts.length < 2) {
      throw new Error('Укажите имя и фамилию через пробел');
    }
    this._firstName = parts[0];
    this._lastName = parts.slice(1).join(' ');
  }

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

  // Только геттер для ageInMonths — вычисляемое свойство
  get ageInMonths(): number {
    return this._age * 12;
  }

  // Сеттер с валидацией
  set age(value: number) {
    if (value < 0 || value > 150) {
      throw new Error('Некорректный возраст');
    }
    this._age = value;
  }
}

// Использование
const user = new User();
user.fullName = "Иван Петров";
user.age = 30;
console.log(user.fullName); // "Иван Петров"
console.log(user.ageInMonths); // 360

Геттеры и сеттеры делают код чище и безопаснее. Главное — помнить про приватные поля и валидацию. Удачи в изучении TypeScript!

<<>>

Альтернативный подход: использование классов без явных геттеров/сеттеров

Не всегда нужно использовать get и set. В TypeScript есть более простые способы инкапсуляции, особенно если вы работаете в команде, где не все знакомы с этой концепцией.

Вариант 1: Обычные методы

class User {
  private _firstName: string = '';
  private _lastName: string = '';

  setFullName(value: string): void {
    const [firstName, ...lastNameParts] = value.split(' ');
    this._firstName = firstName;
    this._lastName = lastNameParts.join(' ') || 'Неизвестно';
  }

  getFullName(): string {
    return `${this._firstName} ${this._lastName}`;
  }
}

const user = new User();
user.setFullName("Иван Петров");
console.log(user.getFullName()); // "Иван Петров"

Плюсы: явно видно, что это методы, нет путаницы с рекурсией.

Вариант 2: Публичные поля с readonly

Если вам нужно только читать вычисляемое значение, можно использовать readonly:

class User {
  firstName: string;
  lastName: string;

  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  readonly fullName: string = ''; // Не сработает!

  // Лучше так: