Event Bus ونمط Observer في TypeScript
نمط Observer (المراقب) وتطبيقه العملي — Event Bus (ناقل الأحداث) — هما أداتان أساسيتان لبناء تطبيقات قابلة للتوسع وذات اقتران ضعيف. في هذه المقالة، سنشرح بالتفصيل كيفية تنفيذ Event Bus بلغة TypeScript باستخدام كتابة الأنواع الصارمة، وسننظر في تطبيقه في المشاريع التعليمية.
ما هو نمط Observer؟
يحدد نمط Observer علاقة "واحد إلى متعدد" بين الكائنات. عندما يغير كائن واحد (الناشر، subject) حالته، يتم إخطار جميع الكائنات التابعة له (المراقبون، observers) وتحديثها تلقائيًا. يتيح ذلك تجنب الاقتران الصارم بين المكونات.
ما هو Event Bus؟
Event Bus (ناقل الأحداث) هو آلية مركزية تنفذ نمط Observer. بدلاً من اشتراك المراقبين مباشرة في الناشر، تتواصل جميع المكونات عبر ناقل واحد. يمكن للمكون:
- الاشتراك (subscribe/listen) في حدث معين.
- نشر (emit/publish) حدث سيتم إرساله إلى جميع المشتركين.
- إلغاء الاشتراك (unsubscribe) من الحدث عندما لا يكون ذلك ضروريًا بعد الآن.
هذا النهج مثالي لـ:
- التفاعل بين الوحدات التي لا ينبغي أن تعرف ببعضها البعض.
- معالجة إجراءات المستخدم (النقرات، إدخال البيانات).
- الاستجابة لتغيرات حالة التطبيق (تحميل البيانات، الأخطاء).
تنفيذ Event Bus بلغة TypeScript
تضيف TypeScript نظام أنواع قوي، مما يجعل Event Bus آمنًا وقابلًا للتنبؤ. سننشئ ناقلًا مكتوبًا حيث يكون لكل حدث مجموعة محددة بدقة من الوسائط.
1. تعريف أنواع الأحداث
أولاً، نحدد واجهة لربط أسماء الأحداث ووسائطها:
// تعريف خريطة الأحداث: المفتاح — اسم الحدث، القيمة — نوع الوسائطinterface EventMap { 'user:login': { userId: string; timestamp: number }; 'data:loaded': { items: string[] }; 'error:occurred': { message: string; code: number }; // يمكن إضافة أي أحداث أخرى}2. إنشاء فئة EventBus
الآن، ننشئ الفئة نفسها باستخدام الأدوية (generics) لكتابة الأنواع الصارمة:
type Listener<T> = (payload: T) => void;
class EventBus<T extends Record<string, unknown>> { private listeners: Map<keyof T, Set<Listener<unknown>>> = new Map();
// الاشتراك في حدث subscribe<K extends keyof T>(event: K, listener: Listener<T[K]>): () => void { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } const currentListeners = this.listeners.get(event)!; currentListeners.add(listener as Listener<unknown>);
// إرجاع دالة لإلغاء الاشتراك return () => { currentListeners.delete(listener as Listener<unknown>); }; }
// نشر حدث emit<K extends keyof T>(event: K, payload: T[K]): void { const currentListeners = this.listeners.get(event); if (currentListeners) { currentListeners.forEach((listener) => { listener(payload); }); } }
// إلغاء الاشتراك من جميع الأحداث (مفيد للتنظيف) clear(): void { this.listeners.clear(); }}
// إنشاء مثيل للناقل مع خريطة الأحداث الخاصة بناexport const appEventBus = new EventBus<EventMap>();3. مثال على الاستخدام
لننظر كيف يمكن للوحدات المختلفة التفاعل عبر Event Bus:
// وحدة المصادقةclass AuthService { login(userId: string) { // ... منطق تسجيل الدخول ... appEventBus.emit('use