مقدمة: لماذا لا يزال موضوع object literal يفجر Stack Overflow؟
\n\nإذا كنت قد كتبت أي كود في TypeScript، فبالتأكيد واجهت موقفًا: تقوم بإنشاء كائن، وتحدد النوع، ويصدر المترجم خطأ، أو الأسوأ من ذلك، لا يصدر خطأ، ولكن كل شيء ينهار في وقت التشغيل. هل هذا مألوف؟
\n\nموضوع type definition in object literal in TypeScript هو أحد أكثر الاستعلامات شيوعًا على Stack Overflow مع آلاف المشاهدات. وهذا ليس من قبيل الصدفة. البساطة الظاهرة تخفي العديد من الفروق الدقيقة: الفرق بين interface و type، الخصائص الزائدة، الحقول الاختيارية، معدّلات readonly، والمشغل الجديد satisfies.
في عام 2025، تقدم TypeScript كثيرًا. ظهرت ميزات تجعل الكود أنظف وأكثر أمانًا. لكن العديد من المطورين لا يزالون يستخدمون أساليب قديمة. دعنا نفهم كيفية كتابة أنواع الكائنات الحرفية بشكل صحيح اليوم، بحيث يكون الكود موثوقًا وقابلاً للقراءة وحديثًا.
\n\n1. Interface أم Type: ماذا تختار لـ object literal في 2025؟
\n\nهذا سؤال كلاسيكي يثير نقاشات حامية. دعنا نضع النقاط على الحروف.
\n\nمتى تستخدم interface
\nInterface هو "المعيار الذهبي" لوصف أشكال الكائنات. يعمل بشكل أفضل مع استنتاج الأنواع، ويعطي رسائل خطأ أكثر وضوحًا، ويدعم declaration merging (دمج التصريحات).
\n\n// ✅ جيد: interface لـ object literal\ninterface User {\n name: string;\n age: number;\n email?: string; // حقل اختياري\n}\n\nconst user: User = {\n name: "آنا",\n age: 28,\n email: "anna@example.com"\n};\n\nمتى تستخدم type
\nType-aliases لا غنى عنها لأنواع union، وأنواع intersection، والمصفوفات الثابتة (tuples)، والأنواع الشرطية المعقدة. بالنسبة للكائنات البسيطة، تعمل أيضًا ولكن لها قيود.
\n\n// ✅ جيد: type للاتحاد\ntype Status = "active" | "inactive" | "pending";\n\n// type للكائن مقبول أيضًا، لكنه أقل قابلية للتوسيع\ntype UserType = {\n name: string;\n age: number;\n};\n\nنصيحة عملية 2025: استخدم interface لواجهات API العامة ووصف أشكال الكائنات. استخدم type للتركيبات المعقدة والأنواع الشرطية وأنواع union. هذه ليست عقيدة، لكنها ممارسة جيدة.
2. الخصائص الزائدة (excess property checks): عدو أم صديق؟
\n\nأحد أكثر أخطاء المبتدئين شيوعًا هو وجود خاصية إضافية في الكائن الحرفي. TypeScript يشتكي، وهذا جيد.
\n\ninterface Config {\n url: string;\n timeout?: number;\n}\n\n// ❌ خطأ: خاصية زائدة 'retries'\nconst badConfig: Config = {\n url: "https://api.example.com",\n timeout: 5000,\n retries: 3 // خطأ!\n};\n\nلكن هناك ثغرة: إذا قمت بتعيين الكائن عبر متغير، يضعف التحقق. كن حذرًا.
\n\nconst configData = {\n url: "https://api.example.com",\n timeout: 5000,\n retries: 3\n};\n\n// ✅ لا يوجد خطأ (لكن هذا قد يكون غير متوقع)\nconst config: Config = configData;\n\nفي عام 2025، استخدم satisfies للتحقق الصارم دون فقدان استنتاج الأنواع. سنتحدث عن هذا في القسم التالي.
3. مشغل satisfies: الميزة الرئيسية لعام 2025
\n\nظهر مشغل satisfies في TypeScript 4.9 وأصبح اختراقًا حقيقيًا. يسمح بالتحقق من أن نوع التعبير يفي بنوع معين، دون تغيير النوع المستنتج نفسه.
// الطريقة القديمة: نفقد النوع الدقيق\nconst palette: Record<string, string | string[]> = {\n red: [255, 0, 0],\n green: "#00ff00",\n blue: "#0000ff"\n};\n// palette.red — string | string[] (سيء)\n\n// الطريقة الجديدة مع satisfies\nconst palette = {\n red: [255, 0, 0],\n green: "#00ff00",\n blue: "#0000ff"\n} satisfies Record<string, string | string[]>;\n\n// palette.red — number[] (تم الحفاظ على النوع الدقيق!)\n// palette.green — string\n// palette.blue — string