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:
- Klašu reģistrēšana atkarību injekcijas (dependency injection) konteinerā.
- Miksīnu vai papildu funkcionalitāšu piemērošana klasei.
- Ietvarstruktūrai specifiskas konfigurācijas (piemēram, maršrutēšana tīmekļa ietvarstruktūrā).
- Dzīves cikla āķu (lifecycle hooks) pievienošana klasēm.
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:
- Metožu izsaukumu un to argumentu/rezultātu reģistrēšana.
- Metožu rezultātu kešošana, lai uzlabotu veiktspēju.
- Autorizācijas pārbaudes piemērošana pirms metodes izpildes.
- Metodes izpildes laika mērīšana.
- Metožu izsaukumu atlikšana (debouncing) vai ierobežošana (throttling).
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:
- Validācija, iestatot īpašību.
- Vērtības transformēšana pirms tās iestatīšanas vai pēc tās iegūšanas.
- Piekļuves atļauju kontrole īpašībām.
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 reģistrēšana serializācijai/deserializācijai.
- Validācijas noteikumu piemērošana īpašībām.
- Noklusējuma vērtību vai konfigurāciju iestatīšana īpašībām.
- ORM (Object-Relational Mapping) kolonnu kartēšana (piem.,
@Column()
TypeORM).
Ī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 tipu reģistrēšana atkarību injekcijai (piem.,
@Inject()
Angular). - Validācijas vai transformācijas piemērošana konkrētiem parametriem.
- Metadatu iegūšana par API pieprasījumu parametriem tīmekļa ietvarstruktūrās.
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:
- Parametru dekoratori tiek piemēroti vispirms, katram parametram, sākot no pēdējā parametra līdz pirmajam.
- Tad katram loceklim tiek piemēroti Metožu, pieejas metožu vai īpašību dekoratori.
- 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ē)
- Lietojiet tos:
- Šķērsgriezuma problēmām: Reģistrēšana, kešošana, autorizācija, transakciju pārvaldība.
- Metadatu deklarēšanai: Shēmas definēšana ORM, validācijas noteikumiem, DI konfigurācijai.
- Ietvarstruktūras integrācijai: Veidojot vai izmantojot ietvarstruktūras, kas izmanto metadatus.
- Atkārtota koda samazināšanai: Abstrahējot atkārtotus koda modeļus.
- Izvairieties no tiem:
- Vienkāršiem funkciju izsaukumiem: Ja vienkāršs funkcijas izsaukums var sasniegt to pašu rezultātu skaidri, dodiet priekšroku tam.
- Biznesa loģikai: Dekoratatoriem vajadzētu papildināt, nevis definēt, galveno biznesa loģiku.
- Pārmērīgai sarežģīšanai: Ja dekoratora izmantošana padara kodu mazāk lasāmu vai grūtāk testējamu, pārdomājiet.
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.