لماذا يعتبر "Date" في TypeScript فخًا؟
\nتخيل: أنت تكتب تطبيقًا، كل شيء يعمل بشكل مثالي. ولكن في يوم من الأيام يأتي تقرير خطأ: "المستخدم من طوكيو يرى التاريخ قبل يوم واحد". تفتح الكود وترى الكلاسيكية: let date: Date = "2024-12-01". أو ما هو أسوأ — any. هل هذا مألوف؟
السؤال "How do I express a date type in TypeScript?" على Stack Overflow حصل على عشرات الآلاف من المشاهدات. وليس من دون سبب. يبدو أن الإجابة بسيطة: استخدم Date. ولكن في الواقع، الأمر أكثر تعقيدًا. TypeScript هي طبقة فوق JavaScript، و Date في JS هو كائن يتصرف... بشكل غريب.
في هذه المقالة، سنقوم بتحليل ليس فقط بناء الجملة، بل الكتابة الصحيحة للتواريخ التي ستنقذ مشروعك من الأخطاء، وتحسن الأداء، وتجعل الكود مفهومًا للفريق. ستتعلم كيفية العمل مع التواريخ الحقيقية في TypeScript دون إضاعة الوقت في التصحيح.
\n\n1. الكتابة الأساسية: Date أم string أم number؟
\nالخيار الأكثر وضوحًا هو النوع المدمج Date:
let birthday: Date = new Date('1990-05-15');\nهل يعمل؟ نعم. ولكن هناك فارق بسيط: TypeScript لا يتحقق مما إذا كان التاريخ صالحًا. يمكنك كتابة new Date('abc') — لن يكون هناك خطأ في الترجمة، ولكن في وقت التشغيل ستحصل على Invalid Date. ومحاولة استدعاء .getTime() ستعيد NaN.
الخيار الأكثر صرامة هو استخدام سلاسل نصية بتنسيق ISO 8601:
\ntype ISODateString = string;\nlet eventDate: ISODateString = '2024-12-01T10:00:00Z';\nالميزة: نوع بسيط، يسهل تسلسله إلى JSON. العيب: لا يوجد أي تحقق — يمكنك تعيين 'hello'، ولن يمانع TS.
الخيار الثالث هو timestamp (number):
\ntype Timestamp = number;\nlet ms: Timestamp = Date.now();\nمثالي للعمليات الحسابية، لكنه غير قابل للقراءة للبشر. ومرة أخرى — أي رقم سيجتاز التحقق.
\n\nملخص الأنواع الأساسية
\nإذا كنت بحاجة إلى كتابة سريعة وغير دقيقة — استخدم Date. إذا كنت تعمل مع API — استخدم string. إذا كنت بحاجة فقط إلى حسابات — استخدم number. ولكن بالنسبة للمشاريع الجادة، هذا غير كافٍ. دعنا نتعمق أكثر.
2. الأنواع المخصصة وكتابة العلامات التجارية (branded types)
\nلمنع TypeScript من الخلط بين سلسلة التاريخ والسلسلة العادية، يتم استخدام الأنواع ذات العلامات التجارية. إنها مثل ملصق "أنا تاريخ، تم التحقق منه!":
\ntype DateString = string & { __brand: 'DateString' };\n\nfunction toDateString(date: Date): DateString {\n return date.toISOString() as DateString;\n}\n\nfunction processDate(d: DateString) {\n // هنا d هي بالتأكيد سلسلة ISO\n}\n\nconst iso = toDateString(new Date());\nprocessDate(iso); // صحيح\n// processDate('hello'); // ❌ خطأ!\nهذا يضيف أمانًا في مرحلة الترجمة. الثمن هو المزيد من الكود قليلاً. ولكن بالنسبة للمشاريع الكبيرة (خاصة إذا كان لديك خدمات مصغرة أو REST API) فهذا هو المعيار الذهبي.
\n\n3. المكتبات للعمل مع التواريخ في TypeScript
\nاستخدام Date الخام هو شر. إنه قابل للتغيير، ويوزع السلاسل بشكل غير متسق، والمناطق الزمنية هي ألم منفصل. لذلك في المشاريع الحقيقية، يتم استخدام المكتبات. إليك ثلاثة مفضلة:
3.1. date-fns (خفيفة ومعيارية)
\nimport { format, parseISO } from 'date-fns';\n\nconst date: Date = parseISO('2024-12-01T10:00:00Z');\nconst formatted: string = format(date, 'dd.MM.yyyy');\nالميزة: tree-shaking، غير قابل للتغيير (يعيد تواريخ جديدة)، كتابة ممتازة.
\n\n3.2. Day.js (بسيطة)
\nimport dayjs, { Dayjs } from 'dayjs';\n\nconst now: Dayjs = dayjs();\nconst formatted: string = now.format('DD.MM.YYYY');\nDay.js تشبه Moment.js، ولكن بدون حزمة ضخمة. النوع Dayjs هو غلاف يسهل تمريره بين الدوال.