TypeScript وسؤاله الأبدي: كيف تجعل النوع nullable؟
\n\nمرحبًا، زميلي! إذا كنت قد كتبت يومًا ما بلغة TypeScript، فبالتأكيد واجهت موقفًا حيث قد ترجع الدالة قيمة، وقد لا ترجع. أو متغير لا تتم تهيئته فورًا. وهنا يطرح السؤال الجوهري: كيف تعلن عن نوع يمكن أن يكون null أو undefined؟
هذا السؤال هو من أكثر الأسئلة شيوعًا على Stack Overflow (عشرات الآلاف من المشاهدات!)، وليس بدون سبب. TypeScript صارم ولكنه عادل. لا يسمح لنا ببساطة بتعيين null لمتغير من النوع string. وهذا رائع لأنه ينقذنا من الكثير من الأخطاء. ولكن ماذا تفعل عندما يكون nullable جزءًا من منطق العمل؟ دعنا نفهم ذلك.
في هذه المقالة، لن نقدم إجابة جافة فقط. سننتقل من الأساسيات إلى التقنيات المتقدمة، ونحلل الأخطاء الشائعة، ونتعلم كيفية كتابة كود آمن وأنيق. هيا بنا!
\n\nالأساسيات: Union Types و Null
\n\nأبسط وأكثر طريقة مباشرة لجعل النوع nullable هي استخدام union type (اتحاد الأنواع). إذا كان المتغير يمكن أن يكون سلسلة نصية أو null، نكتب هكذا:
\n\nlet userName: string | null = null;\nuserName = "أليس"; // صحيح\nuserName = null; // صحيح أيضًا\n\n\nنفس الشيء ينطبق على undefined. في TypeScript، null و undefined هما نوعان مختلفان (إذا لم يتم تفعيل العلم --strictNullChecks، ولكن سنتحدث عنه لاحقًا).
let age: number | undefined;\nage = 25;\nage = undefined; // يعمل\n\n\nمهم: إذا كنت تستخدم | null أو | undefined، فإن TypeScript سيجبرك على التحقق من القيمة قبل الاستخدام. محاولة استدعاء دالة على كائن قد يكون null ستؤدي إلى خطأ في الترجمة. هذه هي "السلامة" التي نحبها في TS.
مثال من الحياة الواقعية: البحث عن مستخدم
\n\ninterface User {\n id: number;\n name: string;\n}\n\nfunction findUser(id: number): User | null {\n // ...منطق البحث\n if (id === 1) {\n return { id: 1, name: "إيفان" };\n }\n return null;\n}\n\nconst user = findUser(2);\n// user.name; // خطأ! 'user' قد يكون null\nif (user) {\n console.log(user.name); // الآن آمن\n}\n\n\nأترى؟ المترجم نفسه يخبرك: "تحقق، وإلا ستسقط!". هذا يوفر ساعات من التصحيح.
\n\nالمعلمات والخصائص الاختيارية (Optional Chaining)
\n\nغالبًا ما تكون nullable ليست فقط المتغيرات، بل أيضًا حقول الكائنات أو وسائط الدوال. لهذا، يوجد في TypeScript صيغة خاصة — optional modifier (علامة الاستفهام ?).
المعلمات الاختيارية للدوال
\n\nfunction greet(name?: string) {\n if (name) {\n return `مرحبًا، ${name}!`;\n }\n return "مرحبًا، أيها الغريب!";\n}\n\nconsole.log(greet("بيتيا")); // مرحبًا، بيتيا!\nconsole.log(greet()); // مرحبًا، أيها الغريب!\n\n\nنوع المعامل name يصبح تلقائيًا string | undefined. لاحظ: إذا كان المعامل اختياريًا، يمكنك عدم تمريره، ولن يشتكي TS.
الخصائص الاختيارية في الواجهات
\n\ninterface Config {\n url: string;\n port?: number; // يمكن أن يكون number أو undefined\n}\n\nconst config1: Config = { url: "http://localhost" }; // port غير موجود\nconst config2: Config = { url: "http://localhost", port: 3000 };\n\n\nعند الوصول إلى config1.port، سيقول TypeScript إنه number | undefined. وهذا صحيح — لا نعرف ما إذا كان المنفذ محددًا.
السلسلة الاختيارية (Optional Chaining) — أفضل صديق لك
\n\nمع ظهور ES2020 و TypeScript 3.7، لدينا طريقة أنيقة للوصول بأمان إلى الخصائص المتداخلة التي قد تكون null أو undefined. وهذا يسمى optional chaining