ts-pattern في TypeScript: دليل شامل مع أمثلة عملية
مكتبة ts-pattern تقدم لمطوري TypeScript أداة قوية ومرنة لمطابقة الأنماط (pattern matching)، مستوحاة من لغات مثل OCaml وRust وHaskell وScala. بدلاً من استخدام سلاسل طويلة ومعقدة من if-else أو switch-case، تتيح لك ts-pattern كتابة منطق التفرع بطريقة تصريحية (declarative) مع التحقق من بنية البيانات وأنواعها. هذا مفيد بشكل خاص عند التعامل مع الأنواع الجبرية للبيانات (ADT) مثل الاتحادات المميزة (discriminated unions) والقيم الاختيارية (Option) أو النتائج (Result).
لماذا تحتاج إلى هذه المكتبة؟ أولاً، يصبح الكود أكثر قابلية للقراءة وأقل تعقيدًا. بدلاً من تحليل الكائنات المتداخلة المعقدة باستخدام العديد من الشروط، تصف بنية البيانات المطلوبة وتترك للمكتبة مهمة إيجاد التطابق المناسب. ثانيًا، توفر ts-pattern فحصًا شاملاً (exhaustiveness check): إذا لم تقم بمعالجة جميع الحالات الممكنة، سيقوم TypeScript بإظهار خطأ أثناء مرحلة الترجمة. هذا يقلل بشكل كبير من الأخطاء الناتجة عن الحالات غير المعالجة. ثالثًا، المكتبة مكتوبة بالكامل مع دعم كامل لأنواع TypeScript، مما يجعل إعادة هيكلة الكود (refactoring) آمنة وقابلة للتوقع.
ts-pattern ليست مجرد بديل لـ switch. إنها أداة متكاملة لتحليل البيانات (data decomposition) تسمح بمطابقة ليس فقط القيم، بل أيضًا الأنواع والهياكل والنطاقات وحتى الشروط المخصصة. تُستخدم المكتبة بنشاط في المشاريع التي تتطلب الموثوقية والتعبيرية: من تطبيقات الواجهة الأمامية (React/Redux) إلى خدمات الخلفية (Node.js).
التثبيت
يتم تثبيت المكتبة عبر مدير الحزم npm (أو yarn/pnpm). تأكد من أن مشروعك يستخدم TypeScript الإصدار 4.0 أو أحدث.
npm install ts-pattern
بعد التثبيت، المكتبة جاهزة للاستخدام. لا حاجة لأي إعدادات إضافية في ملف tsconfig.json.
بداية سريعة
لنبدأ بمثال كلاسيكي — معالجة نتيجة طلب HTTP. لنفترض أن لدينا اتحادًا مميزًا (discriminated union) يصف حالة تحميل البيانات.
import { match } from 'ts-pattern';
// تعريف الحالات الممكنة
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: string }
| { status: 'error'; error: Error };
// دالة تعيد نصًا بناءً على الحالة
function getStatusMessage(state: State): string {
return match(state)
.with({ status: 'idle' }, () => 'انتظار...')
.with({ status: 'loading' }, () => 'جارٍ التحميل...')
.with({ status: 'success' }, ({ data }) => `البيانات: ${data}`)
.with({ status: 'error' }, ({ error }) => `خطأ: ${error.message}`)
.exhaustive(); // التحقق من معالجة جميع الحالات
}
// أمثلة على الاستخدام
console.log(getStatusMessage({ status: 'idle' })); // "انتظار..."
console.log(getStatusMessage({ status: 'loading' })); // "جارٍ التحميل..."
console.log(getStatusMessage({ status: 'success', data: 'مرحبًا بالعالم!' })); // "البيانات: مرحبًا بالعالم!"
console.log(getStatusMessage({ status: 'error', error: new Error('404') })); // "خطأ: 404"
يوضح هذا المثال الفكرة الأساسية: نقوم بمطابقة الكائن state مع أنماط مختلفة. كل نمط هو وصف جزئي لبنية الكائن. إذا تطابقت البنية، يتم تنفيذ الدالة المقابلة. تضمن طريقة .exhaustive() أننا لم ننس معالجة أي من الخيارات.
الدوال والطرق الأساسية
توفر المكتبة عدة دوال وطرق رئيسية. دعنا نستعرضها بالتفصيل.
1. match(value)
التوقيع: match<I, O>(value: I): Match<I, O>
الوظيفة: