useDefineForClassFields în TypeScript

Câmpurile de clasă se comportă ca în standardul JavaScript (definite cu Object.defineProperty), nu ca atribuiri în constructor.

Sintaxă
"compilerOptions": { "useDefineForClassFields": true }

Ce face useDefineForClassFields

Înainte ca JavaScript să aibă câmpuri de clasă, TypeScript le transforma în atribuiri în constructor: this.nume = "necunoscut". Standardul final a ales altă regulă: câmpurile sunt definite (ca Object.defineProperty), chiar și cele fără valoare, care primesc undefined. Diferența se vede la moștenire: un câmp redeclarat în clasa copil suprascrie valoarea pusă de constructorul părintelui.

Cu useDefineForClassFields, TypeScript urmează standardul. Implicit este true pentru target es2022 sau mai nou (inclusiv esnext) și false altfel. Codul vechi care depinde de comportamentul vechi poate avea nevoie de false; pentru un câmp doar redeclarat ca tip, scrie declare pret?: number, care nu generează nimic.

Parametri

Parametru Descriere
true Semantica standard (define). Implicit de la target: es2022.
false Semantica veche (atribuire în constructor).

Exemple

Exemplul 1: Un câmp suprascris după constructorul părintelui

Baza apelează init(), care pune pret = 10. Cu semantica standard, câmpul pret? din Produs este definit după constructorul părintelui și revine la undefined. Cu --useDefineForClassFields false, valoarea 10 rămâne.

// tsconfig.json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext"
  }
}

// index.ts
class Baza {
  constructor() {
    this.init();
  }
  init() {}
}

class Produs extends Baza {
  nume = "necunoscut";
  pret?: number;
  override init() {
    this.pret = 10;
  }
}

const p = new Produs();
console.log(p, Object.keys(p));

Rezultat (tsc 5.9)

$ npx tsc && node index.js
Produs { pret: undefined, nume: 'necunoscut' } [ 'pret', 'nume' ]

$ npx tsc --useDefineForClassFields false && node index.js
Produs { pret: 10, nume: 'necunoscut' } [ 'pret', 'nume' ]

Exemplul 2: Rezolvarea cu declare

declare pret?: number spune doar tipul, fără să genereze un câmp. Valoarea pusă de init() rămâne și cu semantica standard (folosită de exemplele site-ului).

class Baza {
  constructor() {
    this.init();
  }
  init(): void {}
}

class Produs extends Baza {
  nume = "necunoscut";
  declare pret?: number;
  override init(): void {
    this.pret = 10;
  }
}

const p = new Produs();
console.log(p.pret, p.nume);

Rezultat

10 necunoscut

Greșeli frecvente

  • Să schimbi target de la es2020 la es2022 fără să verifici clasele care redeclară câmpuri. Implicitul lui useDefineForClassFields se schimbă odată cu ținta.

Comenzi înrudite

  • target: Versiunea de JavaScript generată: sintaxa mai nouă decât ținta este transformată în cod echivalent mai vechi.
  • strictPropertyInitialization: Dă eroare pentru proprietățile de clasă declarate, dar care nu primesc valoare nici la declarare, nici în constructor.
  • experimentalDecorators: Activează decoratorii vechi (experimentali) ai lui TypeScript, folosiți de Angular și NestJS, în locul celor standard.