Get и Set в TypeScript: магия аксессоров без боли
Вы когда-нибудь писали класс на TypeScript и думали: «Вот бы сделать свойство умным, чтобы при чтении оно что-то вычисляло, а при записи — проверяло данные»? Знакомьтесь — геттеры (get) и сеттеры (set). Это не просто синтаксический сахар, а мощный инструмент для контроля доступа к данным.
Если вы уже сталкивались с этой темой на Stack Overflow, то знаете: вопрос «get and set in TypeScript» собирает тысячи просмотров. Люди путаются, когда применять аксессоры, а когда — обычные методы. В этой статье мы разложим всё по полочкам: от базы до хитрых нюансов.
Готовы? Поехали!
Что такое get и set в TypeScript?
Геттер — это метод, который вызывается при чтении свойства. Сеттер — метод, вызываемый при присвоении значения. Вместе они образуют аксессоры (accessors). Выглядит это так, будто вы работаете с обычным полем, но под капотом — логика.
Вот минимальный пример:
class User {
private _name: string = '';
get name(): string {
return this._name;
}
set name(value: string) {
if (value.length > 0) {
this._name = value;
} else {
console.log('Имя не может быть пустым');
}
}
}
const user = new User();
user.name = 'Анна'; // вызывается set
console.log(user.name); // вызывается get → 'Анна'
user.name = ''; // попытка установить пустое имя → сообщение в консоли
Обратите внимание: свойство _name с префиксом _ — это приватное поле. Прямой доступ к нему извне невозможен. Вместо этого мы используем публичный аксессор name.
Зачем нужны геттеры и сеттеры? 3 главные причины
1. Валидация данных
Сеттер — идеальное место для проверки входных значений. Вы можете отсечь некорректные данные до того, как они попадут в объект.
class Temperature {
private _celsius: number = 0;
get celsius(): number {
return this._celsius;
}
set celsius(value: number) {
if (value < -273.15) {
throw new Error('Температура ниже абсолютного нуля невозможна');
}
this._celsius = value;
}
}
2. Вычисляемые свойства
Геттер может возвращать не сохранённое значение, а результат вычислений на основе других данных.
class Rectangle {
constructor(public width: number, public height: number) {}
get area(): number {
return this.width * this.height;
}
get perimeter(): number {
return 2 * (this.width + this.height);
}
}
const rect = new Rectangle(10, 5);
console.log(rect.area); // 50 — вычисляется на лету
3. Инкапсуляция и контроль доступа
Вы можете сделать свойство доступным только для чтения (только геттер) или только для записи (только сеттер). Это помогает соблюдать принципы ООП.
class Config {
private _apiKey: string = 'secret123';
get apiKey(): string {
// Можно добавить логирование или проверку прав
return this._apiKey;
}
// Сеттер не определён — свойство только для чтения
}
const config = new Config();
console.log(config.apiKey); // 'secret123'
// config.apiKey = 'newKey'; // Ошибка компиляции!
Частые вопросы про get и set в TypeScript
Давайте разберём то, что чаще всего спрашивают новички (и не только) на Stack Overflow и форумах.
Вопрос 1: Чем get/set отличаются от обычных методов?
Главное отличие — синтаксис вызова. Аксессоры выглядят как свойства: без круглых скобок. Это делает код чище и интуитивнее. Сравните:
// С методами
user.getName();
user.setName('Петр');
// С аксессорами
user.name;
user.name = 'Петр';
Второе отличие — аксессоры нельзя перегружать (overload). У каждого аксессора одна сигнатура.
Вопрос 2: Можно ли сделать геттер асинхронным?
Нет, геттеры и сеттеры обязаны быть синхронными. Если вам нужно асинхронное получение данных — используйте обычный метод.
class DataService {
private _data: string = '';
async fetchData(): Promise<string> {
// Асинхронная логика
return 'данные';
}
}
Вопрос 3: Как работает сеттер с типами?
Тип параметра сеттера должен совпадать с типом возврата геттера (если геттер определён). TypeScript сам выводит тип, если вы явно его не указали.
class Score {
private _value: number = 0;
get value(): number {
return this._value;
}
set value(v: number) {
// v должен быть number — иначе ошибка компиляции
this._value = v;
}
}
Вопрос 4: Нужно ли всегда использовать приватное поле?
Да, если вы определяете аксессоры для свойства, которое хранит данные. Без приватного поля вы рискуете создать бесконечную рекурсию:
// ❌ Ошибка!
class BadExample {
get name(): string {
return this.name; // Бесконечный вызов геттера самого себя
}
set name(v: string) {
this.name = v; // Бесконечный вызов сеттера
}
}
Всегда храните данные в отдельном приватном поле с префиксом _.
Продвинутые техники: get и set в реальных проектах
Логирование изменений
С помощью сеттера можно отслеживать, когда и как меняются свойства:
class AuditUser {
private _name: string = '';
get name(): string {
return this._name;
}
set name(value: string) {
console.log(`[${new Date().toISOString()}] Имя изменено на "${value}"`);
this._name = value;
}
}
Ленивая инициализация (Lazy Loading)
Геттер может отложить создание «тяжёлого» объекта до первого обращения:
class HeavyService {
private _data: string[] | null = null;
get data(): string[] {
if (!this._data) {
console.log('Инициализация данных...');
this._data = ['a', 'b', 'c']; // Дорогая операция
}
return this._data;
}
}
const service = new HeavyService();
// Данные ещё не загружены
console.log(service.data); // Первый вызов — инициализация
console.log(service.data); // Второй вызов — просто возврат
Преобразование данных
Сеттер может автоматически преобразовывать входные значения:
class Formatter {
private _text: string = '';
get text(): string {
return this._text.toUpperCase(); // Всегда возвращаем заглавными