TypeScript: زخارف TypeScript
آخر تحديث: 2026-08-26
تُعد «الديكورات» بنية لغوية تجريبية — فهي تستخدم بنية @decorator لإضافة بيانات وصفية إلى الفئات والأساليب والخصائص، أو لتعديل سلوكها. وتُستخدم على نطاق واسع في أطر العمل مثل NestJS وAngular.
1. نظرة عامة على المُزيِّنات
(1) ما المقصود بـ«الديكور»؟
المُزيّن هو دالة — فهي تأخذ هدفًا (فئة، أو طريقة، أو خاصية) كحجة، ويمكنها تعديل سلوك الهدف أو تحسينه:
TYPESCRIPT
// Decorator Functions
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Call ${key},Parameters:${args}`);
return original.apply(this, args);
};
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
let calc = new Calculator();
calc.add(1, 2);
// Output:Call add,Parameters:1,2
(2) تمكين دعم الزخارف
تُعد «الديكورات» ميزة تجريبية ويجب تمكينها في tsconfig.json:
JSON
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
(3) خمسة أنواع من المُزيِّنات
| النوع | الهدف المعدل | المعلمات |
|---|---|---|
| مُزيّن الفئة | تعريف الفئة | مُنشئ |
| زخرفة الأسلوب | أسلوب الفئة | الهدف، اسم الأسلوب، الوصف |
| مُزيّن الخاصية | خاصية الفئة | الهدف، اسم الخاصية |
| مُزيّن المعلمة | معلمة الدالة | الهدف، اسم الطريقة، مؤشر المعلمة |
| مُزيّن مُتَاح | مُسترد/مُعيّن | الهدف، اسم مُتَاح، المُوصِف |
2. مُزيِّنات الفئات
يأخذ مُزيّن الفئة مُنشئًا كحجة، ويمكنه تعديل تعريف الفئة أو استبداله:
(1) الاستخدام الأساسي
TYPESCRIPT
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Config {
host: string = "localhost";
port: number = 3000;
}
// Config Has been sealed——New properties cannot be added
(2) مصنع الديكور
عندما تكون المعلمات مطلوبة، استخدم دالة المصنع لإرجاع زخرفة:
TYPESCRIPT
function className(prefix: string) {
return function (constructor: Function) {
constructor.prototype._displayName = `${prefix}_${constructor.name}`;
};
}
@className("App")
class UserService {}
// UserService.prototype._displayName = "App_UserService"
(3) تجاوز المنشئ
TYPESCRIPT
function logged<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
console.log(`Create ${constructor.name},Parameters:${args}`);
super(...args);
}
};
}
@logged
class User {
constructor(public name: string, public age: number) {}
}
let user = new User("Charlie", 20);
// Output:Create User,Parameters:Charlie,20
▶ مثال: مُزيّن التسجيل — التجميع التلقائي للفئات
TYPESCRIPT
const registry: Map<string, any> = new Map();
function Register(name: string) {
return function <T extends { new (...args: any[]): {} }>(constructor: T) {
registry.set(name, constructor);
return constructor;
};
}
@register("user")
class UserService {
getUser(id: number) { return { id, name: "User" + id }; }
}
@register("product")
class ProductService {
getProduct(id: number) { return { id, title: "Products" + id }; }
}
// Get a Service by Name
function getService(name: string) {
let Service = registry.get(name);
if (!Service) throw new Error(`Unregistered Services:${name}`);
return new Service();
}
let user = (getService("user") as UserService).getUser(1);
let product = (getService("product") as ProductService).getProduct(2);
console.log(user.name); // "User1"
console.log(product.title); // "Products2"
الناتج:
TEXT
📖 للعرض فقط
// Executed successfully
3. زخارف الطرق
يمكن لمُزيِّنات الطرق مراقبة تعريفات الطرق أو تعديلها أو استبدالها:
(1) الاستخدام الأساسي
TYPESCRIPT
function enumerable(value: boolean) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
class Person {
constructor(public name: string) {}
@enumerable(false)
getFullName(): string {
return this.name;
}
}
(2) سجل تنفيذ الأسلوب
TYPESCRIPT
function measure(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = performance.now();
const result = original.apply(this, args);
const end = performance.now();
console.log(`${key} Execution Time:${(end - start).toFixed(2)}ms`);
return result;
};
return descriptor;
}
class DataProcessor {
@measure
processLargeArray(data: number[]): number {
return data.reduce((sum, n) => sum + n, 0);
}
}
let processor = new DataProcessor();
processor.processLargeArray(Array.from({ length: 1000000 }, (_, i) => i));
// Output:processLargeArray Execution Time:XX.XXms
(3) طريقة مكافحة التذبذب
TYPESCRIPT
function debounce(delay: number) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
let timer: any;
descriptor.value = function (...args: any[]) {
clearTimeout(timer);
timer = setTimeout(() => original.apply(this, args), delay);
};
return descriptor;
};
}
class SearchBox {
@debounce(300)
search(query: string): void {
console.log(`Search:${query}`);
}
}
let box = new SearchBox();
box.search("T"); // 300ms Then, if there is no new input, execute it
box.search("Ty");
box.search("Typ");
box.search("TypeScript"); // This will be executed only once
4. مُزيِّنات الخصائص ومُزيِّنات المعلمات
(1) مُزيِّنات الخصائص
TYPESCRIPT
function format(formatStr: string) {
return function (target: any, key: string) {
// Store formatting information in metadata
Reflect.defineMetadata("format", formatStr, target, key);
};
}
class User {
@format("YYYY-MM-DD")
birthday: string = "2000-01-15";
@format("HH:mm:ss")
loginTime: string = "09:30:00";
}
(2) مُزيِّنات المعلمات
TYPESCRIPT
function required(target: any, key: string, index: number) {
const existing: number[] = Reflect.getMetadata("required", target, key) || [];
existing.push(index);
Reflect.defineMetadata("required", existing, target, key);
}
class UserService {
createUser(@required name: string, @required email: string, age?: number) {
// Parameter validation is handled by the framework at runtime.
}
}
5. دمج المُزيِّنات
يمكن استخدام عدة مُزخِّرات في آن واحد — حيث يتم تنفيذها بالترتيب من الأسفل إلى الأعلى (يتم تنفيذ المُزخِّرات الأقرب إلى الهدف أولاً):
TYPESCRIPT
function log1(target: any, key: string, descriptor: PropertyDescriptor) {
console.log("log1 Applications");
}
function log2(target: any, key: string, descriptor: PropertyDescriptor) {
console.log("log2 Applications");
}
class Example {
@log1 // The Second Application(Outer layer)
@log2 // The First Application(Inner layer,Approach Methods)
method() {}
}
// Output:log2 Applications → log1 Applications
(1) أنماط التوليفات الشائعة
TYPESCRIPT
class ApiController {
@logged
@measure
@debounce(100)
async fetchData(url: string): Promise<any> {
// First debounce → then measure → then logged(Bottom-up application)
}
}
▶ مثال: مُزيّن طريقة للقراءة فقط
الناتج:
TEXT
📖 للعرض فقط
User1
Products2
TYPESCRIPT
function readonly(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.writable = false;
return descriptor;
}
class Config {
@readonly
getVersion(): string { return "1.0.0"; }
}
let cfg = new Config();
// cfg.getVersion = () => "2.0.0"; // TypeError: Cannot assign to read-only property
الناتج:
TEXT
📖 للعرض فقط
User1
Products2
▶ مثال: تحذير الطريقة المُهملة
الناتج:
TEXT
📖 للعرض فقط
User1
Products2
TYPESCRIPT
function deprecated(message: string) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.warn(`${key} is deprecated: ${message}`);
return original.apply(this, args);
};
return descriptor;
};
}
class LegacyService {
@deprecated("Use fetchUsers() instead")
getUsers(): string[] { return ["Alice", "Bob"]; }
fetchUsers(): string[] { return ["Alice", "Bob"]; }
}
let svc = new LegacyService();
svc.getUsers(); // getUsers is deprecated: Use fetchUsers() instead
الناتج:
TEXT
📖 للعرض فقط
User1
Products2
❓ أسئلة شائعة
س هل تُعدّ الزخارف ميزة مستقرة؟
ج إنها حاليًا ميزة تجريبية (اقتراح المرحلة 3). ابتداءً من TypeScript 5.0، أصبحت صيغة الديكورات الجديدة للمرحلة 3 مدعومة (ولم تعد تتطلب
experimentalDecorators)، لكن الصيغة القديمة لا تزال متاحة. تستخدم أطر العمل مثل NestJS وAngular حاليًا الصيغة القديمة. يُنصح باتباع الإرشادات التي توفرها إطار العمل الذي تستخدمه.س هل يمكن استخدام الزخارف مع الدوال؟
ج لا. لا يمكن استخدام الزخارف إلا مع الفئات وعناصر الفئات (الأساليب، والخصائص، والمعلمات). لا تدعم الدوال العادية الزخارف — وهذا قيد تصميمي في مقترح لغة جافا سكريبت. إذا كنت بحاجة إلى تحسين دالة ما، فاستخدم نمط الدالة من الدرجة الأعلى (الغلاف).
س ما هو تأثير الزخارف على الأداء؟
ج يتم تنفيذ الزخارف مرة واحدة عند تعريف الفئة (وليس عند كل استدعاء)، لذا فإن العبء الإضافي ضئيل للغاية. أما زخارف الطرق التي تُعدِّل الدالة المُغلفة، فتتسبب في عبء إضافي طفيف عند كل استدعاء (استدعاء دالة إضافي)، لكن هذا العبء عادةً ما يكون ضئيلًا للغاية. في الحالات التي يكون فيها الأداء عاملاً مهمًا، تجنب استخدام طبقات متعددة من الزخارف على المسارات الشائعة الاستخدام.
س هل يمكن تحقيق نفس التأثير دون استخدام الزخارف؟
ج نعم. الديكوريات هي في الأساس «سكر نحوي» —
@log method() تعادل method = log(method). بدون الديكوريات، يمكنك تحقيق نفس النتيجة باستخدام الدوال ذات الترتيب الأعلى، ونمط mixin، ومكتبات AOP، وطرق أخرى. وتتمثل ميزة الديكوريات في أنها إعلانية وبديهية وتؤدي إلى كود أنظف.📖 ملخص
- يستخدم المطورون صيغة
@decoratorلإضافة بيانات وصفية إلى الفئات وعناصر الفئات أو لتعديل سلوكها - خمسة أنواع من المُزيِّنات: الفئات، والطرق، والخصائص، والمعلمات، ووظائف الوصول — ولكل منها توقيع معلمة مختلف
- يُرجع مصنع المُزيّن دالة مُزيّن — مما يسمح للمُزيّن بقبول المعلمات
- يمكن لمُزيّنات الفئات أن تحل محل مُنشِئات الفئات (بإرجاع فئة جديدة)، بينما يمكن لمُزيّنات الطرق أن تُغلف الطرق
- يتم تطبيق الزخارف المتعددة من الأسفل إلى الأعلى (يتم تنفيذ الزخارف الأقرب إلى الهدف أولاً)
- تُعد «الديكورات» ميزة تجريبية تتطلب الخيار
experimentalDecorators
📝 تمارين
- تمرين أساسي (مستوى الصعوبة ⭐): اكتب زخرفة دالة
@readonlyتجعل الدالة غير قابلة للتجاوز (writable: false). قم بتطبيقها على دالة في فئة ما، ثم حاول تجاوز تلك الدالة في فئة فرعية ولاحظ النتيجة. - مشكلة متقدمة (درجة الصعوبة ⭐⭐): اكتب مصنع زخارف
@deprecated(message)يعرض تحذيرًا بشأن الإهمال عند استدعاء الطريقة المزخرفة. تلميح: أضفconsole.warnإلى دالة تغليف الطريقة. - التحدي (الصعوبة: ⭐⭐⭐): قم بتنفيذ حقن التبعية البسيط باستخدام زخرفة الفئة — قم بتمييز فئة الخدمة بـ
@Injectable()، وقم بتمييز خاصية التبعية بـ@Inject(Service)، وسيقومContainer.resolve(TargetClass)تلقائيًا بحل التبعية وحقنها.