tsyringe في TypeScript: دليل شامل مع أمثلة عملية
في تطوير تطبيقات TypeScript الحديثة، تُعد إدارة التبعيات (Dependency Management) من المهام الأساسية، خاصة عند بناء تطبيقات قابلة للتوسع والاختبار. مكتبة tsyringe هي حاوية حقن تبعيات (Dependency Injection - DI) خفيفة الوزن وقوية، مبنية بالكامل على أدوات التزيين (Decorators) في TypeScript. تتيح للمطورين وصف التبعيات بين المكونات بشكل تصريحي، وحلها تلقائيًا، وإدارة دورة حياة الكائنات.
لماذا نحتاج tsyringe؟ في المشاريع الكبيرة، يؤدي الإنشاء اليدوي للكائنات وتمريرها إلى البنائين (Constructors) إلى ترابط شديد في الكود، مما يعقد الاختبار ويصعب استبدال التطبيقات. تحل tsyringe هذه المشكلات من خلال توفير سجل مركزي (حاوية) يعرف كيفية إنشاء وتوفير التبعيات. أنت فقط تحدد الفئات أو الرموز (Tokens) التي يجب تسجيلها، وتقوم الحاوية تلقائيًا بحقنها حيثما تكون مطلوبة. هذا يجعل الكود أنظف، وأكثر نمطية، ويبسط بشكل كبير كتابة اختبارات الوحدة (Unit Tests).
تستخدم المكتبة بشكل نشط أدوات التزيين في TypeScript (@injectable(), @inject())، مما يجعل بناء الجملة بديهيًا للمطورين الملمين بـ Angular أو NestJS. لا تتطلب tsyringe تكوينًا معقدًا، وتعمل "فورًا" مع الإصدارات الحديثة من TypeScript، وتدعم استراتيجيات تسجيل متنوعة (Singleton, Transient, Scoped). في هذه المقالة، سنشرح بالتفصيل جميع جوانب العمل مع tsyringe، بدءًا من التثبيت وصولاً إلى سيناريوهات الاستخدام المتقدمة.
التثبيت
لتثبيت tsyringe في مشروع TypeScript الخاص بك، نفذ الأمر التالي باستخدام npm:
npm install tsyringe reflect-metadata
بعد التثبيت، تأكد من تفعيل دعم أدوات التزيين في مشروعك. للقيام بذلك، يجب تضمين الخيارات التالية في ملف tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
أيضًا، في نقطة الدخول لتطبيقك (مثل index.ts)، يجب استيراد reflect-metadata مرة واحدة لتفعيل البيانات الوصفية لأدوات التزيين:
import "reflect-metadata";
// ... باقي الكود
بداية سريعة: مثال عملي بسيط
لنقم بإنشاء تطبيق بسيط يوضح الآلية الأساسية لعمل tsyringe. سيكون لدينا خدمة للتعامل مع قاعدة بيانات، يتم حقنها في وحدة تحكم (Controller).
import "reflect-metadata";
import { container, injectable, inject } from "tsyringe";
// 1. تعريف الخدمة التي سيتم حقنها
@injectable()
class DatabaseService {
getData(): string {
return "بيانات من قاعدة البيانات";
}
}
// 2. تعريف وحدة التحكم التي تعتمد على DatabaseService
@injectable()
class UserController {
constructor(
@inject(DatabaseService) private dbService: DatabaseService
) {}
getUserData(): string {
return this.dbService.getData();
}
}
// 3. تسجيل الفئات في الحاوية (في هذه الحالة ليس ضروريًا،
// لأن tsyringe يمكنها حل الفئات المسجلة تلقائيًا)
container.registerSingleton(DatabaseService);
container.registerSingleton(UserController);
// 4. استخدام الحاوية للحصول على مثيل من وحدة التحكم
const controller = container.resolve(UserController);
console.log(controller.getUserData()); // سيظهر: "بيانات من قاعدة البيانات"
يوضح هذا المثال ثلاث خطوات رئيسية: وضع علامة @injectable() على الفئة، تحديد التبعيات عبر @inject() في الباني (Constructor)، والحصول على المثيل عبر container.resolve(). لاحظ أنه إذا تم تسجيل الفئة باستخدام أداة التزيين @injectable()، فإن tsyringe يمكنها حلها تلقائيًا دون تسجيل صريح في الحاوية.