Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Що змінює useDefineForClassFields і чому поля класу інколи «затирають» значення з батьківського конструктора?

Поля класу в TypeScript з'явилися раніше, ніж стандартні поля класів у JavaScript, і TypeScript довго компілював їх як присвоєння в конструкторі. Стандарт же визначає поля через Object.defineProperty - з іншою семантикою.

useDefineForClassFields перемикає на стандартну семантику. Він увімкнений за замовчуванням, коли target - ES2022 чи новіший (а з TypeScript 6 типова ціль - поточна версія ECMAScript, тож для нових проєктів це майже завжди так).

Де різниця стає помітною:

1. Поле без ініціалізатора в нащадку затирає значення з батька:

class Base {
  name: string;
  constructor() {
    this.name = 'base';
    this.init();
  }
  init() {}
}

class Child extends Base {
  name!: string;   // лише уточнення типу
}

new Child().name;   // зі стандартною семантикою - undefined!

Оголошене в нащадку поле визначається заново (зі значенням undefined) після батьківського конструктора. Рішення - declare name: string;: це оголошення лише для типів, без поля під час виконання.

Компілятор попереджає про обидва випадки нижче: «Property 'name' will overwrite the base property... add a 'declare' modifier» (TS2612) і «'value' is defined as an accessor in class..., but is overridden here as an instance property» (TS2610). Ці помилки не варто глушити - вони описують реальну зміну поведінки.

2. Поля перекривають сетери батька:

class Base {
  set value(v: number) { console.log('setter', v); }
}
class Child extends Base {
  value = 1;   // стандарт: власна властивість, сетер не викликається
}

3. Порядок ініціалізації з parameter properties: поля ініціалізуються до тіла конструктора, і поле, що використовує parameter property, може побачити undefined.

4. Бібліотеки з декораторами чи реактивністю (старі MobX, деякі ORM), що покладалися на присвоєння й сетери прототипу, ламаються зі стандартною семантикою.

Що робити:

  • у новому коді - лишати стандартну семантику (так поводиться і браузер, і Node.js, і будь-який інший транспілятор);
  • для перевизначення типу поля в нащадку - declare, а не повторне оголошення;
  • при міграції старого коду - перевірити класи з успадкуванням полів і сетерів; тимчасово можна вимкнути прапорець, але це розходження з реальним JavaScript.

Чому це не «дрібниця налаштувань»: код, що працює однаково в тестах (одна ціль компіляції) і по-різному в продакшені (інша), - одна з найнеочевидніших помилок при зміні target.

Докладніше в документації: useDefineForClassFields

Схожі питання