Treci la conținutul principal

Fuzionarea declarațiilor în TypeScript

Fuzionarea declarațiilor este ceea ce face TypeScript atunci când găsește mai multe declarații cu același nume: le combină într-o singură definiție.


Înțelegerea fuzionării declarațiilor

Fuzionarea declarațiilor îți permite să combini mai multe declarații care au același nume într-o singură definiție. Astfel poți construi tipuri complexe pas cu pas și poți extinde tipuri existente fără să pierzi siguranța tipurilor.

Beneficii principale:

  • Îmbunătățire progresivă: construiești un tip treptat, în mai multe declarații.
  • Extensibilitate: adaugi membri la un tip existent fără să atingi definiția lui inițială.
  • Organizare: împarți un tip mare în grupuri logice.
  • Compatibilitate: extinzi, la nevoie, definițiile de tipuri ale bibliotecilor externe.

Cazuri de utilizare frecvente:

  • Extinderea tipurilor predefinite și a tipurilor din biblioteci externe.
  • Adăugarea informațiilor de tip pentru biblioteci JavaScript.
  • Organizarea interfețelor mari în mai multe fișiere.
  • Crearea de API-uri fluente, cu înlănțuirea metodelor.
  • Implementarea modelului de augmentare a modulelor.

Fuzionarea interfețelor

Interfețele cu același nume sunt fuzionate automat. Rezultatul conține toți membrii din toate declarațiile.

Exemplu

Cele două declarații Person devin o singură interfață cu name, age, address și email, așa că un obiect trebuie să furnizeze toate cele patru proprietăți.

Prompt
Arată că două declarații interface Person din același fișier sunt fuzionate într-un singur tip. Prima declară name: string și age: number, a doua declară address: string și email: string. Creează un obiect person cu toate cele patru proprietăți (John, 30, "123 Main St", [email protected]) și afișează întregul obiect.
Ieșire: program TypeScript și rezultatul capturat

Supraîncărcarea funcțiilor prin fuzionare

Poți scrie mai multe semnături de funcție cu același nume. Ele fuzionează într-o singură funcție supraîncărcată, iar o singură implementare tratează toate cazurile.

Exemplu

processValue are trei semnături de supraîncărcare și o singură implementare. Semnăturile îi spun apelantului exact ce tip de retur corespunde fiecărui tip de argument.

Prompt
Declară trei semnături de supraîncărcare pentru processValue — una care primește și returnează string, una number și una boolean — urmate de o singură implementare care acceptă string | number | boolean. Implementarea transformă șirurile în majuscule, dublează numerele și neagă valorile booleene. Apeleaz-o cu "hello", 10 și true și afișează fiecare rezultat.
Ieșire: program TypeScript și rezultatul capturat

Fuzionarea spațiilor de nume

Și spațiile de nume cu același nume sunt fuzionate. Membrii exportați dintr-un bloc sunt vizibili în celelalte.

Exemplu

Al doilea bloc Validation poate implementa StringValidator din primul bloc, iar codul din exterior ajunge la tot ce există prin unicul spațiu de nume fuzionat.

Prompt
Arată cum două blocuri namespace Validation fuzionează într-un singur spațiu de nume. Primul exportă o interfață StringValidator cu isValid(s: string): boolean; al doilea exportă o interfață NumberValidator și o clasă ZipCodeValidator care implementează StringValidator, verificând dacă șirul are exact cinci cifre. Creează un ZipCodeValidator prin Validation.ZipCodeValidator și afișează isValid pentru "12345", "1234" și "abcde".
Ieșire: program TypeScript și rezultatul capturat

Fuzionarea claselor cu interfețele

O declarație de clasă poate fuziona cu o interfață care are același nume. Interfața adaugă membri la tipul clasei, iar clasa trebuie să îi implementeze.

Fuzionarea unei clase cu o interfață

Interfața Cart declară calculateTotal(), iar clasa Cart o furnizează, împreună cu propriii membri items și addItem.

script.ts
// Interface declaration
interface Cart {
  calculateTotal(): number;
}

// Class declaration with same name
class Cart {
  items: { name: string; price: number }[] = [];

  addItem(name: string, price: number): void {
    this.items.push({ name, price });
  }

    // Must implement the interface method
    calculateTotal(): number {
    return this.items.reduce((sum, item) => sum + item.price, 0);
  }
}

// Using the merged class and interface
const cart = new Cart();
cart.addItem("Book", 15.99);
cart.addItem("Coffee Mug", 8.99);

console.log(`Total: $${cart.calculateTotal().toFixed(2)}`);

Fuzionarea enumerărilor

Și declarațiile de enumerare cu același nume sunt fuzionate. Doar primul membru din prima declarație poate să nu aibă valoare; declarațiile ulterioare trebuie să stabilească valori explicite.

Exemplu

Direction ajunge să aibă patru membri. Căutarea inversă după valoare (Direction[0]) funcționează pe toată enumerarea fuzionată.

Prompt
Arată cum două declarații enum Direction fuzionează într-o singură enumerare. Prima declară North și South (numerotate automat 0 și 1); a doua declară East = 2 și West = 3. Afișează valoarea numerică a tuturor celor patru membri, apoi afișează Direction[0] și Direction[2], ca să arăți căutarea inversă după valoare.
Ieșire: program TypeScript și rezultatul capturat

Augmentarea modulelor

Poți extinde un modul sau o bibliotecă existentă declarând tipuri și funcții suplimentare cu același nume. Acest lucru se numește augmentarea modulelor.

Exemplu

Primul bloc declare namespace LibraryModule ține locul unei biblioteci externe. Al doilea bloc adaugă o interfață nouă, o proprietate opțională nouă pe User și o funcție nouă. Deoarece blocurile declare nu conțin cod care se execută, exemplul atașează o mică implementare substitut la globalThis, ca să poată rula.

Prompt
Arată augmentarea modulelor cu două blocuri declare namespace LibraryModule: primul (care ține locul unei biblioteci externe) declară o interfață User (id, name) și o funcție getUser(id: number): User; al doilea adaugă o interfață UserPreferences (theme, notifications), adaugă o proprietate opțională preferences?: UserPreferences pe User și declară getUserPreferences(userId: number): UserPreferences. Deoarece blocurile declare nu conțin cod care se execută, atașează la globalThis o mică implementare substitut a lui LibraryModule, ca fișierul să poată rula. Apelează getUser(123) și afișează user.preferences?.theme, apoi apelează getUserPreferences(123) și afișează prefs.notifications.
Ieșire: program TypeScript și rezultatul capturat

Bune practici

Există câteva reguli de reținut atunci când folosești fuzionarea declarațiilor:

  • Ordinea contează la supraîncărcarea funcțiilor: semnătura implementării ar trebui să fie cea mai generală.
  • Membrii care nu sunt funcții trebuie să fie compatibili: dacă două interfețe declară o proprietate cu același nume, tipurile trebuie să fie identice sau compatibile.
  • Interfețele declarate mai târziu au prioritate: dacă interfețele fuzionate intră în conflict, câștigă ultima declarație.
  • Membrii private și protected: clasele nu pot fuziona dacă au membri private sau protected cu același nume, dar cu tipuri diferite.
  • Exporturile din spațiile de nume: după fuzionare, doar declarațiile exportate sunt vizibile în afara spațiului de nume.

Aspecte legate de performanță

  • Timpul de compilare: folosirea intensă a fuzionării declarațiilor poate încetini compilarea.
  • Verificarea tipurilor: tipurile fuzionate complexe pot încetini editorul.
  • Dimensiunea bundle-ului: fuzionarea declarațiilor nu are niciun efect asupra performanței la rulare sau asupra dimensiunii bundle-ului.

Sfaturi de optimizare:

  • Păstrează interfețele fuzionate concentrate și coerente.
  • Evită imbricarea adâncă în tipurile fuzionate.
  • Folosește alias-uri type pentru combinații simple de tipuri, în locul fuzionării.