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، وطرق أخرى. وتتمثل ميزة الديكوريات في أنها إعلانية وبديهية وتؤدي إلى كود أنظف.

📖 ملخص

📝 تمارين

  1. تمرين أساسي (مستوى الصعوبة ⭐): اكتب زخرفة دالة @readonly تجعل الدالة غير قابلة للتجاوز (writable: false). قم بتطبيقها على دالة في فئة ما، ثم حاول تجاوز تلك الدالة في فئة فرعية ولاحظ النتيجة.
  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): اكتب مصنع زخارف @deprecated(message) يعرض تحذيرًا بشأن الإهمال عند استدعاء الطريقة المزخرفة. تلميح: أضف console.warn إلى دالة تغليف الطريقة.
  3. التحدي (الصعوبة: ⭐⭐⭐): قم بتنفيذ حقن التبعية البسيط باستخدام زخرفة الفئة — قم بتمييز فئة الخدمة بـ @Injectable()، وقم بتمييز خاصية التبعية بـ @Inject(Service)، وسيقوم Container.resolve(TargetClass) تلقائيًا بحل التبعية وحقنها.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%