Latviešu

Atklājiet TypeScript dekoratoru spēku metadatu programmēšanā, aspektorientētajā programmēšanā un koda uzlabošanā ar deklaratīviem modeļiem. Visaptverošs ceļvedis globāliem izstrādātājiem.

TypeScript Dekoratatori: Metadatu Programmēšanas Modeļu Apgūšana Izturīgu Lietojumprogrammu Izveidei

Plašajā mūsdienu programmatūras izstrādes ainavā tīru, mērogojamu un pārvaldāmu kodu bāzu uzturēšana ir ārkārtīgi svarīga. TypeScript ar savu spēcīgo tipu sistēmu un uzlabotajām funkcijām sniedz izstrādātājiem rīkus, lai to sasniegtu. Starp tās intriģējošākajām un pārveidojošākajām funkcijām ir Dekoratatori. Lai gan šī raksta tapšanas brīdī tā joprojām ir eksperimentāla funkcija (ECMAScript 3. posma priekšlikums), dekoratori tiek plaši izmantoti tādās ietvarstruktūrās kā Angular un TypeORM, fundamentāli mainot veidu, kā mēs pieejam dizaina modeļiem, metadatu programmēšanai un aspektorientētajai programmēšanai (AOP).

Šis visaptverošais ceļvedis dziļi ienirs TypeScript dekoratoros, pētot to mehāniku, dažādos veidus, praktiskos pielietojumus un labākās prakses. Neatkarīgi no tā, vai veidojat liela mēroga uzņēmuma lietojumprogrammas, mikropakalpojumus vai klienta puses tīmekļa saskarnes, dekoratoru izpratne ļaus jums rakstīt deklaratīvāku, uzturamāku un jaudīgāku TypeScript kodu.

Pamatjēdziena Izpratne: Kas ir Dekoratators?

Savā būtībā dekorators ir īpašs deklarācijas veids, ko var pievienot klases deklarācijai, metodei, pieejas metodei (accessor), īpašībai vai parametram. Dekoratatori ir funkcijas, kas atgriež jaunu vērtību (vai modificē esošo) mērķim, ko tās dekorē. To galvenais mērķis ir pievienot metadatus vai mainīt deklarācijas uzvedību, kurai tie ir pievienoti, tieši nemainot pamatā esošo koda struktūru. Šis ārējais, deklaratīvais koda papildināšanas veids ir neticami spēcīgs.

Uztveriet dekoratorus kā anotācijas vai iezīmes, ko piemērojat sava koda daļām. Šīs iezīmes pēc tam var nolasīt vai uz tām reaģēt citas jūsu lietojumprogrammas daļas vai ietvarstruktūras, bieži vien izpildlaikā, lai nodrošinātu papildu funkcionalitāti vai konfigurāciju.

Dekoratora Sintakse

Dekoratoriem priekšā ir @ simbols, kam seko dekoratora funkcijas nosaukums. Tie tiek novietoti tieši pirms deklarācijas, kuru tie dekorē.

@MyDecorator
class MyClass {
  @AnotherDecorator
  myMethod() {
    // ...
  }
}

Dekoratoru Iespējošana TypeScript

Pirms varat izmantot dekoratorus, jums ir jāiespējo experimentalDecorators kompilatora opcija savā tsconfig.json failā. Papildus tam, lai izmantotu uzlabotas metadatu refleksijas iespējas (ko bieži izmanto ietvarstruktūras), jums būs nepieciešams arī emitDecoratorMetadata un reflect-metadata polifils.

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2017",
    "module": "commonjs",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Jums ir arī jāinstalē reflect-metadata:

npm install reflect-metadata --save
# vai
yarn add reflect-metadata

Un importējiet to pašā lietojumprogrammas ieejas punkta sākumā (piemēram, main.ts vai app.ts):

import "reflect-metadata";
// Jūsu lietojumprogrammas kods seko

Dekoratoru Fabrikas: Pielāgošana Jūsu Rokas Tvērienā

Lai gan vienkāršs dekorators ir funkcija, bieži vien jums būs nepieciešams nodot argumentus dekoratoram, lai konfigurētu tā darbību. To panāk, izmantojot dekoratoru fabriku. Dekoratoru fabrika ir funkcija, kas atgriež faktisko dekoratora funkciju. Kad jūs piemērojat dekoratoru fabriku, jūs to izsaucat ar tās argumentiem, un tā pēc tam atgriež dekoratora funkciju, ko TypeScript piemēro jūsu kodam.

Vienkārša Dekoratoru Fabrikas Piemēra Izveide

Izveidosim fabriku Logger dekoratoram, kas var reģistrēt ziņojumus ar dažādiem prefiksiem.

function Logger(prefix: string) {
  return function (target: Function) {
    console.log(`[${prefix}] Klase ${target.name} ir definēta.`);
  };
}

@Logger("APP_INIT")
class ApplicationBootstrap {
  constructor() {
    console.log("Lietojumprogramma startē...");
  }
}

const app = new ApplicationBootstrap();
// Izvade:
// [APP_INIT] Klase ApplicationBootstrap ir definēta.
// Lietojumprogramma startē...

Šajā piemērā Logger("APP_INIT") ir dekoratoru fabrikas izsaukums. Tas atgriež faktisko dekoratora funkciju, kas kā argumentu saņem target: Function (klases konstruktoru). Tas ļauj dinamiski konfigurēt dekoratora darbību.

Dekoratoru Tipi TypeScript

TypeScript atbalsta piecus atšķirīgus dekoratoru tipus, katrs no tiem ir piemērojams noteikta veida deklarācijai. Dekoraatora funkcijas paraksts mainās atkarībā no konteksta, kurā tas tiek lietots.

1. Klases Dekoratatori

Klases dekoratori tiek piemēroti klašu deklarācijām. Dekoraatora funkcija saņem klases konstruktoru kā savu vienīgo argumentu. Klases dekorators var novērot, modificēt vai pat aizstāt klases definīciju.

Paraksts:

function ClassDecorator(target: Function) { ... }

Atgrieztā Vērtība:

Ja klases dekorators atgriež vērtību, tas aizstās klases deklarāciju ar norādīto konstruktora funkciju. Šī ir spēcīga funkcija, ko bieži izmanto miksīniem (mixins) vai klases papildināšanai. Ja vērtība netiek atgriezta, tiek izmantota sākotnējā klase.

Pielietojuma Gadījumi:

Klases Dekoratora Piemērs: Pakalpojuma Injekcija

Iedomājieties vienkāršu atkarību injekcijas scenāriju, kurā vēlaties atzīmēt klasi kā "injicējamu" (injectable) un pēc izvēles norādīt tās nosaukumu konteinerā.

const InjectableServiceRegistry = new Map<string, Function>();

function Injectable(name?: string) {
  return function<T extends { new(...args: any[]): {} }>(constructor: T) {
    const serviceName = name || constructor.name;
    InjectableServiceRegistry.set(serviceName, constructor);
    console.log(`Reģistrēts pakalpojums: ${serviceName}`);

    // Pēc izvēles šeit varētu atgriezt jaunu klasi, lai papildinātu uzvedību
    return class extends constructor {
      createdAt = new Date();
      // Papildu īpašības vai metodes visiem injicētajiem pakalpojumiem
    };
  };
}

@Injectable("UserService")
class UserDataService {
  getUsers() {
    return [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
  }
}

@Injectable()
class ProductDataService {
  getProducts() {
    return [{ id: 101, name: "Laptop" }, { id: 102, name: "Mouse" }];
  }
}

console.log("--- Pakalpojumi Reģistrēti ---");
console.log(Array.from(InjectableServiceRegistry.keys()));

const userServiceConstructor = InjectableServiceRegistry.get("UserService");
if (userServiceConstructor) {
  const userServiceInstance = new userServiceConstructor();
  console.log("Lietotāji:", userServiceInstance.getUsers());
  // console.log("Lietotāju Pakalpojums Izveidots:", userServiceInstance.createdAt); // Ja tiek izmantota atgrieztā klase
}

Šis piemērs demonstrē, kā klases dekorators var reģistrēt klasi un pat modificēt tās konstruktoru. Injectable dekorators padara klasi atklājamu teorētiskai atkarību injekcijas sistēmai.

2. Metožu Dekoratatori

Metožu dekoratori tiek piemēroti metožu deklarācijām. Tie saņem trīs argumentus: mērķa objektu (statiskiem locekļiem - konstruktora funkciju; instances locekļiem - klases prototipu), metodes nosaukumu un metodes īpašību deskriptoru.

Paraksts:

function MethodDecorator(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) { ... }

Atgrieztā Vērtība:

Metodes dekorators var atgriezt jaunu PropertyDescriptor. Ja tas to dara, šis deskriptors tiks izmantots metodes definēšanai. Tas ļauj jums modificēt vai aizstāt sākotnējās metodes implementāciju, padarot to neticami spēcīgu priekš AOP.

Pielietojuma Gadījumi:

Metodes Dekoratora Piemērs: Veiktspējas Monitorings

Izveidosim MeasurePerformance dekoratoru, lai reģistrētu metodes izpildes laiku.

function MeasurePerformance(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function(...args: any[]) {
    const start = process.hrtime.bigint();
    const result = originalMethod.apply(this, args);
    const end = process.hrtime.bigint();
    const duration = Number(end - start) / 1_000_000;
    console.log(`Metode "${propertyKey}" izpildīta ${duration.toFixed(2)} ms`);
    return result;
  };

  return descriptor;
}

class DataProcessor {
  @MeasurePerformance
  processData(data: number[]): number[] {
    // Simulē sarežģītu, laikietilpīgu operāciju
    for (let i = 0; i < 1_000_000; i++) {
      Math.sin(i);
    }
    return data.map(n => n * 2);
  }

  @MeasurePerformance
  fetchRemoteData(id: string): Promise<string> {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(`Dati ID: ${id}`);
      }, 500);
    });
  }
}

const processor = new DataProcessor();
processor.processData([1, 2, 3]);
processor.fetchRemoteData("abc").then(result => console.log(result));

MeasurePerformance dekorators ietin sākotnējo metodi ar laika mērīšanas loģiku, izdrukājot izpildes ilgumu, nepiesārņojot pašu metodes biznesa loģiku. Tas ir klasisks aspektorientētās programmēšanas (AOP) piemērs.

3. Pieejas Metožu (Accessor) Dekoratatori

Pieejas metožu dekoratori tiek piemēroti pieejas metožu (get un set) deklarācijām. Līdzīgi kā metožu dekoratori, tie saņem mērķa objektu, pieejas metodes nosaukumu un tās īpašību deskriptoru.

Paraksts:

function AccessorDecorator(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) { ... }

Atgrieztā Vērtība:

Pieejas metodes dekorators var atgriezt jaunu PropertyDescriptor, kas tiks izmantots pieejas metodes definēšanai.

Pielietojuma Gadījumi:

Pieejas Metodes Dekoratora Piemērs: Get metožu kešošana

Izveidosim dekoratoru, kas kešo dārga gettera aprēķina rezultātu.

function CachedGetter(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalGetter = descriptor.get;
  const cacheKey = `_cached_${String(propertyKey)}`;

  if (originalGetter) {
    descriptor.get = function() {
      if (this[cacheKey] === undefined) {
        console.log(`[Kešatmiņas Kļūda] Aprēķina vērtību ${String(propertyKey)}`);
        this[cacheKey] = originalGetter.apply(this);
      } else {
        console.log(`[Kešatmiņas Trāpījums] Izmanto kešoto vērtību ${String(propertyKey)}`);
      }
      return this[cacheKey];
    };
  }
  return descriptor;
}

class ReportGenerator {
  private data: number[];

  constructor(data: number[]) {
    this.data = data;
  }

  // Simulē dārgu aprēķinu
  @CachedGetter
  get expensiveSummary(): number {
    console.log("Veic dārgu kopsavilkuma aprēķinu...");
    return this.data.reduce((sum, current) => sum + current, 0) / this.data.length;
  }
}

const generator = new ReportGenerator([10, 20, 30, 40, 50]);

console.log("Pirmā piekļuve:", generator.expensiveSummary);
console.log("Otrā piekļuve:", generator.expensiveSummary);
console.log("Trešā piekļuve:", generator.expensiveSummary);

Šis dekorators nodrošina, ka expensiveSummary gettera aprēķins tiek veikts tikai vienu reizi, nākamie izsaukumi atgriež kešoto vērtību. Šis modelis ir ļoti noderīgs veiktspējas optimizācijai, kur īpašuma piekļuve ietver smagus aprēķinus vai ārējus izsaukumus.

4. Īpašību Dekoratatori

Īpašību dekoratori tiek piemēroti īpašību deklarācijām. Tie saņem divus argumentus: mērķa objektu (statiskiem locekļiem - konstruktora funkciju; instances locekļiem - klases prototipu) un īpašības nosaukumu.

Paraksts:

function PropertyDecorator(target: Object, propertyKey: string | symbol) { ... }

Atgrieztā Vērtība:

Īpašību dekoratori nevar atgriezt nekādu vērtību. To galvenais pielietojums ir reģistrēt metadatus par īpašību. Tie nevar tieši mainīt īpašības vērtību vai tās deskriptoru dekorēšanas brīdī, jo īpašības deskriptors vēl nav pilnībā definēts, kad tiek izpildīti īpašību dekoratori.

Pielietojuma Gadījumi:

Īpašību Dekoratora Piemērs: Obligātā Lauka Validācija

Izveidosim dekoratoru, lai atzīmētu īpašību kā "obligātu" un pēc tam to validētu izpildlaikā.

interface ValidationRule {
  property: string | symbol;
  validate: (value: any) => boolean;
  message: string;
}

const validationRules: Map<Function, ValidationRule[]> = new Map();

function Required(target: Object, propertyKey: string | symbol) {
  const rules = validationRules.get(target.constructor) || [];
  rules.push({
    property: propertyKey,
    validate: (value: any) => value !== null && value !== undefined && value !== "",
    message: `${String(propertyKey)} ir obligāts lauks.`
  });
  validationRules.set(target.constructor, rules);
}

function validate(instance: any): string[] {
  const classRules = validationRules.get(instance.constructor) || [];
  const errors: string[] = [];

  for (const rule of classRules) {
    if (!rule.validate(instance[rule.property])) {
      errors.push(rule.message);
    }
  }
  return errors;
}

class UserProfile {
  @Required
  firstName: string;

  @Required
  lastName: string;

  age?: number;

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

const user1 = new UserProfile("John", "Doe", 30);
console.log("Lietotāja 1 validācijas kļūdas:", validate(user1)); // []

const user2 = new UserProfile("", "Smith");
console.log("Lietotāja 2 validācijas kļūdas:", validate(user2)); // ["firstName ir obligāts lauks."]

const user3 = new UserProfile("Alice", "");
console.log("Lietotāja 3 validācijas kļūdas:", validate(user3)); // ["lastName ir obligāts lauks."]

Required dekorators vienkārši reģistrē validācijas noteikumu centrālā validationRules kartē. Atsevišķa validate funkcija pēc tam izmanto šos metadatus, lai pārbaudītu instanci izpildlaikā. Šis modelis atdala validācijas loģiku no datu definīcijas, padarot to atkārtoti lietojamu un tīru.

5. Parametru Dekoratatori

Parametru dekoratori tiek piemēroti parametriem klases konstruktorā vai metodē. Tie saņem trīs argumentus: mērķa objektu (statiskiem locekļiem - konstruktora funkciju; instances locekļiem - klases prototipu), metodes nosaukumu (vai undefined konstruktora parametriem) un parametra kārtas indeksu funkcijas parametru sarakstā.

Paraksts:

function ParameterDecorator(target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) { ... }

Atgrieztā Vērtība:

Parametru dekoratori nevar atgriezt nekādu vērtību. Tāpat kā īpašību dekoratoriem, to galvenā loma ir pievienot metadatus par parametru.

Pielietojuma Gadījumi:

Parametru Dekoratora Piemērs: Pieprasījuma Datu Injekcija

Simulēsim, kā tīmekļa ietvarstruktūra varētu izmantot parametru dekoratorus, lai injicētu konkrētus datus metodes parametrā, piemēram, lietotāja ID no pieprasījuma.

interface ParameterMetadata {
  index: number;
  key: string | symbol;
  resolver: (request: any) => any;
}

const parameterResolvers: Map<Function, Map<string | symbol, ParameterMetadata[]>> = new Map();

function RequestParam(paramName: string) {
  return function (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) {
    const targetKey = propertyKey || "constructor";
    let methodResolvers = parameterResolvers.get(target.constructor);
    if (!methodResolvers) {
      methodResolvers = new Map();
      parameterResolvers.set(target.constructor, methodResolvers);
    }
    const paramMetadata = methodResolvers.get(targetKey) || [];
    paramMetadata.push({
      index: parameterIndex,
      key: targetKey,
      resolver: (request: any) => request[paramName]
    });
    methodResolvers.set(targetKey, paramMetadata);
  };
}

// Hipotētiska ietvarstruktūras funkcija, lai izsauktu metodi ar atrisinātiem parametriem
function executeWithParams(instance: any, methodName: string, request: any) {
  const classResolvers = parameterResolvers.get(instance.constructor);
  if (!classResolvers) {
    return (instance[methodName] as Function).apply(instance, []);
  }
  const methodParamMetadata = classResolvers.get(methodName);
  if (!methodParamMetadata) {
    return (instance[methodName] as Function).apply(instance, []);
  }

  const args: any[] = Array(methodParamMetadata.length);
  for (const meta of methodParamMetadata) {
    args[meta.index] = meta.resolver(request);
  }
  return (instance[methodName] as Function).apply(instance, args);
}

class UserController {
  getUser(@RequestParam("id") userId: string, @RequestParam("token") authToken?: string) {
    console.log(`Iegūst lietotāju ar ID: ${userId}, Žetons: ${authToken || "N/A"}`);
    return { id: userId, name: "Jane Doe" };
  }

  deleteUser(@RequestParam("id") userId: string) {
    console.log(`Dzēš lietotāju ar ID: ${userId}`);
    return { status: "deleted", id: userId };
  }
}

const userController = new UserController();

// Simulē ienākošu pieprasījumu
const mockRequest = {
  id: "user123",
  token: "abc-123",
  someOtherProp: "xyz"
};

console.log("\n--- Izpilda getUser ---");
executeWithParams(userController, "getUser", mockRequest);

console.log("\n--- Izpilda deleteUser ---");
executeWithParams(userController, "deleteUser", { id: "user456" });

Šis piemērs parāda, kā parametru dekoratori var apkopot informāciju par nepieciešamajiem metodes parametriem. Ietvarstruktūra pēc tam var izmantot šos apkopotos metadatus, lai automātiski atrisinātu un injicētu atbilstošas vērtības, kad tiek izsaukta metode, ievērojami vienkāršojot kontroliera vai pakalpojuma loģiku.

Dekoratoru Kompozīcija un Izpildes Kārtība

Dekoratorus var piemērot dažādās kombinācijās, un to izpildes kārtības izpratne ir būtiska, lai prognozētu uzvedību un izvairītos no negaidītām problēmām.

Vairāki Dekoratatori uz Viena Mērķa

Kad vienai deklarācijai (piemēram, klasei, metodei vai īpašībai) tiek piemēroti vairāki dekoratori, tie tiek izpildīti noteiktā secībā: to novērtēšana notiek no apakšas uz augšu vai no labās uz kreiso. Tomēr to rezultāti tiek piemēroti pretējā secībā.

@DecoratorA
@DecoratorB
class MyClass {
  // ...
}

Šeit DecoratorB tiks novērtēts vispirms, pēc tam DecoratorA. Ja tie modificē klasi (piemēram, atgriežot jaunu konstruktoru), DecoratorA modifikācija aptvers vai tiks piemērota virs DecoratorB modifikācijas.

Piemērs: Metožu Dekoratoru Sasaiste

Apsveriet divus metožu dekoratorus: LogCall un Authorization.

function LogCall(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`[LOG] Izsauc ${String(propertyKey)} ar argumentiem:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`[LOG] Metode ${String(propertyKey)} atgrieza:`, result);
    return result;
  };
  return descriptor;
}

function Authorization(roles: string[]) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const currentUserRoles = ["admin"]; // Simulē pašreizējā lietotāja lomu iegūšanu
      const authorized = roles.some(role => currentUserRoles.includes(role));
      if (!authorized) {
        console.warn(`[AUTH] Piekļuve liegta ${String(propertyKey)}. Nepieciešamās lomas: ${roles.join(", ")}`);
        throw new Error("Neautorizēta piekļuve");
      }
      console.log(`[AUTH] Piekļuve atļauta ${String(propertyKey)}`);
      return originalMethod.apply(this, args);
    };
    return descriptor;
  };
}

class SecureService {
  @LogCall
  @Authorization(["admin"])
  deleteSensitiveData(id: string) {
    console.log(`Dzēš sensitīvus datus ar ID: ${id}`);
    return `Datu ID ${id} dzēsts.`;
  }

  @Authorization(["user"])
  @LogCall // Šeit mainīta kārtība
  fetchPublicData(query: string) {
    console.log(`Iegūst publiskus datus ar vaicājumu: ${query}`);
    return `Publiski dati vaicājumam: ${query}`; 
  }
}

const service = new SecureService();

try {
  console.log("\n--- Izsauc deleteSensitiveData (Admina lietotājs) ---");
  service.deleteSensitiveData("record123");
} catch (error: any) {
  console.error(error.message);
}

try {
  console.log("\n--- Izsauc fetchPublicData (Ne-admina lietotājs) ---");
  // Simulē ne-admina lietotāju, kurš mēģina piekļūt fetchPublicData, kam nepieciešama 'user' loma
  const mockUserRoles = ["guest"]; // Šis neizdosies autorizācijā
  // Lai padarītu to dinamisku, būtu nepieciešama DI sistēma vai statisks konteksts pašreizējā lietotāja lomām.
  // Vienkāršības labad mēs pieņemam, ka Authorization dekoratoram ir piekļuve pašreizējam lietotāja kontekstam.
  // Pielāgosim Authorization dekoratoru, lai tas demonstrācijas nolūkos vienmēr pieņemtu 'admin',
  // lai pirmais izsaukums būtu veiksmīgs, bet otrais neizdotos, parādot dažādus ceļus.
  
  // Palaist vēlreiz ar lietotāja lomu, lai fetchPublicData izdotos.
  // Iedomājieties, ka currentUserRoles Authorization kļūst: ['user']
  // Šajā piemērā saglabāsim vienkāršību un parādīsim kārtības efektu.
  service.fetchPublicData("search term"); // Šis izpildīs Auth -> Log
} catch (error: any) {
  console.error(error.message);
}

/* Paredzamā izvade deleteSensitiveData:
[AUTH] Piekļuve atļauta deleteSensitiveData
[LOG] Izsauc deleteSensitiveData ar argumentiem: [ 'record123' ]
Dzēš sensitīvus datus ar ID: record123
[LOG] Metode deleteSensitiveData atgrieza: Datu ID record123 dzēsts.
*/

/* Paredzamā izvade fetchPublicData (ja lietotājam ir 'user' loma):
[LOG] Izsauc fetchPublicData ar argumentiem: [ 'search term' ]
[AUTH] Piekļuve atļauta fetchPublicData
Iegūst publiskus datus ar vaicājumu: search term
[LOG] Metode fetchPublicData atgrieza: Publiski dati vaicājumam: search term
*/

Ievērojiet secību: deleteSensitiveData gadījumā Authorization (apakšā) tiek izpildīts pirmais, pēc tam LogCall (augšā) to aptver. Authorization iekšējā loģika izpildās pirmā. fetchPublicData gadījumā LogCall (apakšā) tiek izpildīts pirmais, pēc tam Authorization (augšā) to aptver. Tas nozīmē, ka LogCall aspekts būs ārpus Authorization aspekta. Šī atšķirība ir kritiska šķērsgriezuma problēmām, piemēram, reģistrēšanai vai kļūdu apstrādei, kur izpildes secība var būtiski ietekmēt uzvedību.

Izpildes Kārtība Dažādiem Mērķiem

Kad klasei, tās locekļiem un parametriem visiem ir dekoratori, izpildes secība ir skaidri definēta:

  1. Parametru dekoratori tiek piemēroti vispirms, katram parametram, sākot no pēdējā parametra līdz pirmajam.
  2. Tad katram loceklim tiek piemēroti Metožu, pieejas metožu vai īpašību dekoratori.
  3. Visbeidzot, pašai klasei tiek piemēroti Klases dekoratori.

Katrā kategorijā vairāki dekoratori uz viena un tā paša mērķa tiek piemēroti no apakšas uz augšu (vai no labās uz kreiso).

Piemērs: Pilna Izpildes Kārtība

function log(message: string) {
  return function (target: any, propertyKey: string | symbol | undefined, descriptorOrIndex?: PropertyDescriptor | number) {
    if (typeof descriptorOrIndex === 'number') {
      console.log(`Parametra dekorators: ${message} uz parametra #${descriptorOrIndex} no ${String(propertyKey || "constructor")}`);
    } else if (typeof propertyKey === 'string' || typeof propertyKey === 'symbol') {
      if (descriptorOrIndex && 'value' in descriptorOrIndex && typeof descriptorOrIndex.value === 'function') {
        console.log(`Metodes/Pieejas metodes dekorators: ${message} uz ${String(propertyKey)}`);
      } else {
        console.log(`Īpašības dekorators: ${message} uz ${String(propertyKey)}`);
      }
    } else {
      console.log(`Klases dekorators: ${message} uz ${target.name}`);
    }
    return descriptorOrIndex; // Atgriež deskriptoru metodei/pieejas metodei, undefined citiem
  };
}

@log("Klases līmenis D")
@log("Klases līmenis C")
class MyDecoratedClass {
  @log("Statiska īpašība A")
  static staticProp: string = "";

  @log("Instances īpašība B")
  instanceProp: number = 0;

  @log("Metode D")
  @log("Metode C")
  myMethod(
    @log("Parametrs Z") paramZ: string,
    @log("Parametrs Y") paramY: number
  ) {
    console.log("Metode myMethod izpildīta.");
  }

  @log("Getter/Setter F")
  get myAccessor() {
    return "";
  }

  set myAccessor(value: string) {
    //...
  }

  constructor() {
    console.log("Konstruktors izpildīts.");
  }
}

new MyDecoratedClass();
// Izsauc metodi, lai aktivizētu metodes dekoratoru
new MyDecoratedClass().myMethod("hello", 123);

/* Paredzamā izvades kārtība (aptuvena, atkarībā no konkrētās TypeScript versijas un kompilācijas):
Parametra dekorators: Parametrs Y uz parametra #1 no myMethod
Parametra dekorators: Parametrs Z uz parametra #0 no myMethod
Īpašības dekorators: Statiska īpašība A uz staticProp
Īpašības dekorators: Instances īpašība B uz instanceProp
Metodes/Pieejas metodes dekorators: Getter/Setter F uz myAccessor
Metodes/Pieejas metodes dekorators: Metode C uz myMethod
Metodes/Pieejas metodes dekorators: Metode D uz myMethod
Klases dekorators: Klases līmenis C uz MyDecoratedClass
Klases dekorators: Klases līmenis D uz MyDecoratedClass
Konstruktors izpildīts.
Metode myMethod izpildīta.
*/

Precīzs konsoles žurnāla laiks var nedaudz atšķirties atkarībā no tā, kad tiek izsaukts konstruktors vai metode, bet secība, kādā pašas dekoratoru funkcijas tiek izpildītas (un tādējādi to blakusefekti vai atgrieztās vērtības tiek piemērotas), atbilst iepriekš minētajiem noteikumiem.

Praktiski Pielietojumi un Dizaina Modeļi ar Dekoratatoriem

Dekoratori, īpaši kopā ar reflect-metadata polifilu, paver jaunu metadatu vadītas programmēšanas jomu. Tas ļauj izmantot jaudīgus dizaina modeļus, kas abstrahē lieko kodu un šķērsgriezuma problēmas.

1. Atkarību Injekcija (DI)

Viens no visizplatītākajiem dekoratoru pielietojumiem ir atkarību injekcijas ietvarstruktūrās (piemēram, Angular @Injectable(), @Component() utt. vai NestJS plašā DI izmantošana). Dekoratori ļauj deklarēt atkarības tieši uz konstruktoriem vai īpašībām, ļaujot ietvarstruktūrai automātiski izveidot un nodrošināt pareizos pakalpojumus.

Piemērs: Vienkāršota Pakalpojumu Injekcija

import "reflect-metadata"; // Būtiski priekš emitDecoratorMetadata

const INJECTABLE_METADATA_KEY = Symbol("injectable");
const INJECT_METADATA_KEY = Symbol("inject");

function Injectable() {
  return function (target: Function) {
    Reflect.defineMetadata(INJECTABLE_METADATA_KEY, true, target);
  };
}

function Inject(token: any) {
  return function (target: Object, propertyKey: string | symbol, parameterIndex: number) {
    const existingInjections: any[] = Reflect.getOwnMetadata(INJECT_METADATA_KEY, target, propertyKey) || [];
    existingInjections[parameterIndex] = token;
    Reflect.defineMetadata(INJECT_METADATA_KEY, existingInjections, target, propertyKey);
  };
}

class Container {
  private static instances = new Map<any, any>();

  static resolve<T>(target: { new (...args: any[]): T }): T {
    if (Container.instances.has(target)) {
      return Container.instances.get(target);
    }

    const isInjectable = Reflect.getMetadata(INJECTABLE_METADATA_KEY, target);
    if (!isInjectable) {
      throw new Error(`Klase ${target.name} nav atzīmēta kā @Injectable.`);
    }

    // Iegūst konstruktora parametru tipus (nepieciešams emitDecoratorMetadata)
    const paramTypes: any[] = Reflect.getMetadata("design:paramtypes", target) || [];
    const explicitInjections: any[] = Reflect.getMetadata(INJECT_METADATA_KEY, target) || [];

    const dependencies = paramTypes.map((paramType, index) => {
      // Izmanto skaidru @Inject žetonu, ja norādīts, citādi secina tipu
      const token = explicitInjections[index] || paramType;
      if (token === undefined) {
        throw new Error(`Nevar atrisināt parametru ar indeksu ${index} priekš ${target.name}. Tā varētu būt cirkulāra atkarība vai primitīvs tips bez skaidras @Inject norādes.`);
      }
      return Container.resolve(token);
    });

    const instance = new target(...dependencies);
    Container.instances.set(target, instance);
    return instance;
  }
}

// Definē pakalpojumus
@Injectable()
class DatabaseService {
  connect() {
    console.log("Savienojas ar datubāzi...");
    return "DB Savienojums";
  }
}

@Injectable()
class AuthService {
  private db: DatabaseService;

  constructor(db: DatabaseService) {
    this.db = db;
  }

  login() {
    console.log(`AuthService: Autentificējas, izmantojot ${this.db.connect()}`);
    return "Lietotājs ir ielogojies";
  }
}

@Injectable()
class UserService {
  private authService: AuthService;
  private dbService: DatabaseService; // Piemērs injekcijai caur īpašību, izmantojot pielāgotu dekoratoru vai ietvarstruktūras funkciju

  constructor(@Inject(AuthService) authService: AuthService,
              @Inject(DatabaseService) dbService: DatabaseService) {
    this.authService = authService;
    this.dbService = dbService;
  }

  getUserProfile() {
    this.authService.login();
    this.dbService.connect();
    console.log("UserService: Iegūst lietotāja profilu...");
    return { id: 1, name: "Global User" };
  }
}

// Atrisina galveno pakalpojumu
console.log("--- Atrisina UserService ---");
const userService = Container.resolve(UserService);
console.log(userService.getUserProfile());

console.log("\n--- Atrisina AuthService (vajadzētu būt kešotam) ---");
const authService = Container.resolve(AuthService);
authService.login();

Šis sarežģītais piemērs demonstrē, kā @Injectable un @Inject dekoratori kopā ar reflect-metadata ļauj pielāgotam Container automātiski atrisināt un nodrošināt atkarības. design:paramtypes metadati, ko automātiski izstaro TypeScript (kad emitDecoratorMetadata ir patiess), šeit ir kritiski svarīgi.

2. Aspektorientētā Programmēšana (AOP)

AOP koncentrējas uz šķērsgriezuma problēmu (piem., reģistrēšana, drošība, transakcijas) modularizāciju, kas skar vairākas klases un moduļus. Dekoratori ir lieliski piemēroti AOP koncepciju ieviešanai TypeScript.

Piemērs: Žurnālēšana ar Metodes Dekoratoru

Atgriežoties pie LogCall dekoratora, tas ir ideāls AOP piemērs. Tas pievieno reģistrēšanas uzvedību jebkurai metodei, nemainot metodes sākotnējo kodu. Tas atdala "ko darīt" (biznesa loģika) no "kā to darīt" (reģistrēšana, veiktspējas uzraudzība utt.).

function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`[LOG AOP] Ieiet metodē: ${String(propertyKey)} ar argumentiem:`, args);
    try {
      const result = originalMethod.apply(this, args);
      console.log(`[LOG AOP] Iziet no metodes: ${String(propertyKey)} ar rezultātu:`, result);
      return result;
    } catch (error: any) {
      console.error(`[LOG AOP] Kļūda metodē ${String(propertyKey)}:`, error.message);
      throw error;
    }
  };
  return descriptor;
}

class PaymentProcessor {
  @LogMethod
  processPayment(amount: number, currency: string) {
    if (amount <= 0) {
      throw new Error("Maksājuma summai jābūt pozitīvai.");
    }
    console.log(`Apstrādā maksājumu ${amount} ${currency}...`);
    return `Maksājums ${amount} ${currency} veiksmīgi apstrādāts.`;
  }

  @LogMethod
  refundPayment(transactionId: string) {
    console.log(`Atmaksā maksājumu par transakcijas ID: ${transactionId}...`);
    return `Atmaksa uzsākta transakcijai ${transactionId}.`;
  }
}

const processor = new PaymentProcessor();
processor.processPayment(100, "USD");
try {
  processor.processPayment(-50, "EUR");
} catch (error: any) {
  console.error("Noķerta kļūda:", error.message);
}

Šī pieeja uztur PaymentProcessor klasi koncentrētu tikai uz maksājumu loģiku, kamēr LogMethod dekorators apstrādā reģistrēšanas šķērsgriezuma problēmu.

3. Validācija un Transformācija

Dekoratori ir neticami noderīgi, lai definētu validācijas noteikumus tieši uz īpašībām vai lai transformētu datus serializācijas/deserializācijas laikā.

Piemērs: Datu Validācija ar Īpašību Dekoratatoriem

Iepriekšējais @Required piemērs to jau demonstrēja. Šeit ir vēl viens piemērs ar skaitlisku diapazona validāciju.

interface FieldValidationRule {
  property: string | symbol;
  validator: (value: any) => boolean;
  message: string;
}

const fieldValidationRules = new Map<Function, FieldValidationRule[]>();

function addValidationRule(target: Object, propertyKey: string | symbol, validator: (value: any) => boolean, message: string) {
  const rules = fieldValidationRules.get(target.constructor) || [];
  rules.push({ property: propertyKey, validator, message });
  fieldValidationRules.set(target.constructor, rules);
}

function IsPositive(target: Object, propertyKey: string | symbol) {
  addValidationRule(target, propertyKey, (value: number) => value > 0, `${String(propertyKey)} jābūt pozitīvam skaitlim.`);
}

function MaxLength(maxLength: number) {
  return function (target: Object, propertyKey: string | symbol) {
    addValidationRule(target, propertyKey, (value: string) => value.length <= maxLength, `${String(propertyKey)} drīkst būt ne garāks par ${maxLength} rakstzīmēm.`);
  };
}

class Product {
  @MaxLength(50)
  name: string;

  @IsPositive
  price: number;

  constructor(name: string, price: number) {
    this.name = name;
    this.price = price;
  }

  static validate(instance: any): string[] {
    const errors: string[] = [];
    const rules = fieldValidationRules.get(instance.constructor) || [];
    for (const rule of rules) {
      if (!rule.validator(instance[rule.property])) {
        errors.push(rule.message);
      }
    }
    return errors;
  }
}

const product1 = new Product("Laptop", 1200);
console.log("Produkta 1 kļūdas:", Product.validate(product1)); // []

const product2 = new Product("Ļoti garš produkta nosaukums, kas pārsniedz piecdesmit rakstzīmju ierobežojumu testēšanas nolūkos", 50);
console.log("Produkta 2 kļūdas:", Product.validate(product2)); // ["name drīkst būt ne garāks par 50 rakstzīmēm."]

const product3 = new Product("Grāmata", -10);
console.log("Produkta 3 kļūdas:", Product.validate(product3)); // ["price jābūt pozitīvam skaitlim."]

Šis iestatījums ļauj deklaratīvi definēt validācijas noteikumus jūsu modeļa īpašībām, padarot jūsu datu modeļus pašaprakstošus attiecībā uz to ierobežojumiem.

Labākās Prakses un Apsvērumi

Lai gan dekoratori ir spēcīgi, tie jālieto apdomīgi. To nepareiza izmantošana var radīt kodu, kas ir grūtāk atkļūdojams vai saprotams.

Kad Lietot Dekoratatorus (un Kad Nē)

Veiktspējas Ietekme

Dekoratori tiek izpildīti kompilēšanas laikā (vai definēšanas laikā JavaScript izpildlaikā, ja tie tiek transpilēti). Transformācija vai metadatu apkopošana notiek, kad tiek definēta klase/metode, nevis katrā izsaukumā. Tāpēc dekoratoru *piemērošanas* ietekme uz izpildlaika veiktspēju ir minimāla. Tomēr *loģika jūsu dekoratoros* var ietekmēt veiktspēju, īpaši, ja tie veic dārgas operācijas katrā metodes izsaukumā (piem., sarežģīti aprēķini metodes dekoratorā).

Uzturamība un Lasāmība

Dekoratori, pareizi lietoti, var ievērojami uzlabot lasāmību, pārvietojot lieko kodu no galvenās loģikas. Tomēr, ja tie veic sarežģītas, slēptas transformācijas, atkļūdošana var kļūt sarežģīta. Pārliecinieties, ka jūsu dekoratori ir labi dokumentēti un to uzvedība ir paredzama.

Eksperimentālais Statuss un Dekoratatoru Nākotne

Ir svarīgi atkārtot, ka TypeScript dekoratori ir balstīti uz 3. posma TC39 priekšlikumu. Tas nozīmē, ka specifikācija lielākoties ir stabila, bet joprojām varētu piedzīvot nelielas izmaiņas, pirms kļūst par oficiālu ECMAScript standarta daļu. Ietvarstruktūras, piemēram, Angular, ir tās pieņēmušas, paļaujoties uz to galīgo standartizāciju. Tas nozīmē noteiktu riska līmeni, lai gan, ņemot vērā to plašo izplatību, būtiskas izmaiņas, kas salauztu saderību, ir maz ticamas.

TC39 priekšlikums ir attīstījies. TypeScript pašreizējā implementācija ir balstīta uz vecāku priekšlikuma versiju. Pastāv atšķirība starp "Mantotajiem dekoratoriem" un "Standarta dekoratoriem". Kad tiks pieņemts oficiālais standarts, TypeScript, visticamāk, atjauninās savu implementāciju. Lielākajai daļai izstrādātāju, kas izmanto ietvarstruktūras, šo pāreju pārvaldīs pati ietvarstruktūra. Bibliotēku autoriem varētu būt nepieciešams izprast smalkās atšķirības starp mantotajiem un nākotnes standarta dekoratoriem.

Kompilatora Opcija emitDecoratorMetadata

Šī opcija, ja tā ir iestatīta uz true tsconfig.json, liek TypeScript kompilatoram emitēt noteiktus dizaina laika tipu metadatus kompilētajā JavaScript. Šie metadati ietver konstruktora parametru tipu (design:paramtypes), metožu atgriešanas tipu (design:returntype) un īpašību tipu (design:type).

Šie emitētie metadati nav standarta JavaScript izpildlaika daļa. Tos parasti patērē reflect-metadata polifils, kas pēc tam padara tos pieejamus, izmantojot Reflect.getMetadata() funkcijas. Tas ir absolūti kritiski sarežģītiem modeļiem, piemēram, atkarību injekcijai, kur konteineram ir jāzina, kādus atkarību tipus klasei nepieciešams bez skaidras konfigurācijas.

Sarežģītāki Modeļi ar Dekoratatoriem

Dekoratorus var apvienot un paplašināt, lai veidotu vēl sarežģītākus modeļus.

1. Dekoratatoru Dekorēšana (Augstākas Kārtas Dekoratatori)

Jūs varat izveidot dekoratorus, kas modificē vai veido citus dekoratorus. Tas ir retāk sastopams, bet demonstrē dekoratoru funkcionālo dabu.

// Dekoratators, kas nodrošina, ka metode tiek žurnālēta un prasa arī admina lomas
function AdminAndLoggedMethod() {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    // Vispirms piemēro Authorization (iekšējo)
    Authorization(["admin"])(target, propertyKey, descriptor);
    // Pēc tam piemēro LogCall (ārējo)
    LogCall(target, propertyKey, descriptor);

    return descriptor; // Atgriež modificēto deskriptoru
  };
}

class AdminPanel {
  @AdminAndLoggedMethod()
  deleteUserAccount(userId: string) {
    console.log(`Dzēš lietotāja kontu: ${userId}`);
    return `Lietotājs ${userId} dzēsts.`;
  }
}

const adminPanel = new AdminPanel();
adminPanel.deleteUserAccount("user007");
/* Paredzamā izvade (pieņemot, ka ir admina loma):
[AUTH] Piekļuve atļauta deleteUserAccount
[LOG] Izsauc deleteUserAccount ar argumentiem: [ 'user007' ]
Dzēš lietotāja kontu: user007
[LOG] Metode deleteUserAccount atgrieza: Lietotājs user007 dzēsts.
*/

Šeit AdminAndLoggedMethod ir fabrika, kas atgriež dekoratoru, un šajā dekoratorā tā piemēro divus citus dekoratorus. Šis modelis var iekapsulēt sarežģītas dekoratoru kompozīcijas.

2. Dekoratatoru Izmantošana Miksīniem (Mixins)

Lai gan TypeScript piedāvā citus veidus, kā ieviest miksīnus, dekoratorus var izmantot, lai deklaratīvi injicētu spējas klasēs.

function ApplyMixins(constructors: Function[]) {
  return function (derivedConstructor: Function) {
    constructors.forEach(baseConstructor => {
      Object.getOwnPropertyNames(baseConstructor.prototype).forEach(name => {
        Object.defineProperty(
          derivedConstructor.prototype,
          name,
          Object.getOwnPropertyDescriptor(baseConstructor.prototype, name) || Object.create(null)
        );
      });
    });
  };
}

class Disposable {
  isDisposed: boolean = false;
  dispose() {
    this.isDisposed = true;
    console.log("Objekts atbrīvots.");
  }
}

class Loggable {
  log(message: string) {
    console.log(`[Loggable] ${message}`);
  }
}

@ApplyMixins([Disposable, Loggable])
class MyResource implements Disposable, Loggable {
  // Šīs īpašības/metodes tiek injicētas ar dekoratoru
  isDisposed!: boolean;
  dispose!: () => void;
  log!: (message: string) => void;

  constructor(public name: string) {
    this.log(`Resurss ${this.name} izveidots.`);
  }

  cleanUp() {
    this.dispose();
    this.log(`Resurss ${this.name} sakopts.`);
  }
}

const resource = new MyResource("NetworkConnection");
console.log(`Vai atbrīvots: ${resource.isDisposed}`);
resource.cleanUp();
console.log(`Vai atbrīvots: ${resource.isDisposed}`);

Šis @ApplyMixins dekorators dinamiski kopē metodes un īpašības no bāzes konstruktoriem uz atvasinātās klases prototipu, efektīvi "iemaisot" funkcionalitātes.

Noslēgums: Mūsdienu TypeScript Izstrādes Veicināšana

TypeScript dekoratori ir jaudīga un izteiksmīga funkcija, kas paver jaunu metadatu vadītas un aspektorientētas programmēšanas paradigmu. Tie ļauj izstrādātājiem uzlabot, modificēt un pievienot deklaratīvas uzvedības klasēm, metodēm, īpašībām, pieejas metodēm un parametriem, nemainot to galveno loģiku. Šī interešu atdalīšana noved pie tīrāka, uzturamāka un ļoti atkārtoti lietojama koda.

No atkarību injekcijas vienkāršošanas un robustu validācijas sistēmu ieviešanas līdz šķērsgriezuma problēmu, piemēram, reģistrēšanas un veiktspējas uzraudzības, pievienošanai, dekoratori nodrošina elegantu risinājumu daudziem izplatītiem izstrādes izaicinājumiem. Lai gan to eksperimentālais statuss prasa uzmanību, to plašā izplatība lielākajās ietvarstruktūrās liecina par to praktisko vērtību un nākotnes nozīmi.

Apgūstot TypeScript dekoratorus, jūs iegūstat nozīmīgu rīku savā arsenālā, kas ļauj veidot izturīgākas, mērogojamākas un viedākas lietojumprogrammas. Izmantojiet tos atbildīgi, izprotiet to mehāniku un atveriet jaunu deklaratīvās jaudas līmeni savos TypeScript projektos.