{"id":"l-rf-1-3","courseId":"react-fundamentals","lang":"ar","kind":"lesson","summary":"اكتب components في React على شكل دوال عادية، وضع بعضها داخل بعض، وقسّمها إلى ملفات باستخدام import وexport، وتجنّب أخطاء التسمية والتداخل التي تكسرها.","html":"<p>حاليًا يحتوي <code dir=\"ltr\">App.jsx</code> على عنوان واحد. أمّا شاشة Watchlist الحقيقية ففيها ترويسة ونموذج وشريط فلترة وقائمة من بطاقات الأفلام، وبعض القطع تتكرّر مرات كثيرة. إن كتبت كل ذلك في دالة واحدة فستحصل على component من 300 سطر لا يرغب أحد في لمسه. الـ components تتيح لك أن تسمّي كل قطعة، وتكتبها مرة واحدة، وتعيد استخدامها.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الـ component مجرّد دالة</h2>\n<p>هذا component كامل:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">src/App.jsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>My Watchlist&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Movies I want to see&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> App</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>ثلاث قواعد تجعل هذا يعمل:</p>\n<ol>\n<li><strong>إنه دالة تُرجع JSX.</strong> الـ JSX هو الصياغة الشبيهة بـ HTML داخل <code dir=\"ltr\">return</code>. ستتعلّم قواعده في الدرس التالي.</li>\n<li><strong>اسمه يبدأ بحرف كبير.</strong> الوسم <code dir=\"ltr\">&lt;Header /&gt;</code> يقول لـ React «استدعِ الدالة Header». أمّا <code dir=\"ltr\">&lt;header&gt;</code> بالحرف الصغير فيعني عنصر HTML.</li>\n<li><strong>تستخدمه كوسم، لا كاستدعاء دالة.</strong> تكتب <code dir=\"ltr\">&lt;Header /&gt;</code> وReact تقرّر متى تستدعي <code dir=\"ltr\">Header()</code>. وهذا ما يتيح لـ React أن تتتبّعه وتعيد رسمه (re-render) وتمنحه state لاحقًا.</li>\n</ol>\n<p>الأقواس حول الـ JSX متعدّد الأسطر ليست شرطًا من React. وظيفتها منع JavaScript من إدراج فاصلة منقوطة تلقائيًا تُنهي <code dir=\"ltr\">return</code> مبكرًا، فتُرجع <code dir=\"ltr\">undefined</code> بصمت.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">components داخل components</h2>\n<p>تتداخل الـ components كما يتداخل HTML. أضف بطاقة فيلم واستخدمها مرتين:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">src/App.jsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> MovieCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Arrival&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>2016&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> App</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">MovieCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">MovieCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>لديك الآن بطاقتان متطابقتان. واضح أن هذا ليس مفيدًا بعد؛ يجب أن تعرض البطاقات أفلامًا مختلفة. تمرير البيانات إلى الـ component هو وظيفة الـ props، وستصل إليها في القسم 2. الآن لاحظ الشكل فقط: <code dir=\"ltr\">App</code> هو الأب، و<code dir=\"ltr\">Header</code> والبطاقتان <code dir=\"ltr\">MovieCard</code> أبناؤه. كل تطبيق React شجرة من هذا النوع.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>تعريف component داخل component آخر</span></p>\n<p>من المغري أن تكتب <code dir=\"ltr\">function Badge() {…}</code> داخل <code dir=\"ltr\">MovieCard</code> لأن <code dir=\"ltr\">MovieCard</code> وحده يستخدمه. لا تفعل. كل render لـ <code dir=\"ltr\">MovieCard</code> ينشئ دالة <code dir=\"ltr\">Badge</code> جديدة، فترى React نوع component مختلفًا، وتهدم الـ DOM الخاص بـ Badge وأي state بداخله ثم تعيد إنشاءهما. عرّف الـ components دائمًا في المستوى الأعلى من الملف.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">component واحد لكل ملف</h2>\n<p>عندما يُعاد استخدام component أو يكبر حتى يتجاوز شاشة واحدة، امنحه ملفًا خاصًا به. الترتيب المعتاد هو مجلد <code dir=\"ltr\">src/components</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">src/components/MovieCard.jsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> MovieCard</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Arrival&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>2016&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">src/App.jsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Header </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> './components/Header.jsx'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> MovieCard </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> './components/MovieCard.jsx'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> App</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">MovieCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>تمنحك JavaScript modules نوعين من التصدير:</p>\n<ul>\n<li><strong>التصدير الافتراضي (default export):</strong> <code dir=\"ltr\">export default function MovieCard()</code>. واحد لكل ملف، ويُستورد بلا أقواس: <code dir=\"ltr\">import MovieCard from '…'</code>.</li>\n<li><strong>التصدير المُسمّى (named export):</strong> <code dir=\"ltr\">export function MovieCard()</code>. أي عدد في الملف، ويُستورد بأقواس: <code dir=\"ltr\">import { MovieCard } from '…'</code>.</li>\n</ul>\n<p>كلاهما جيد. كثير من الفرق تفضّل الـ named exports لأن الاسم ثابت، والمحرّرات تعيد تسميته في كل مكان نيابةً عنك. اختر أسلوبًا واحدًا لكل مشروع والتزم به. تستخدم هذه الدورة الـ default exports للـ components، تماشيًا مع الملف الذي أنشأه Vite.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>سمِّ الملفات بأسماء الـ components</span></p>\n<p>الملف <code dir=\"ltr\">MovieCard.jsx</code> يصدّر <code dir=\"ltr\">MovieCard</code>. وحين يقول بلاغ خطأ إن «بطاقة الفيلم تعرض السنة الخاطئة»، تعرف أي ملف تفتح من دون بحث.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">الـ components يجب أن تكون متوقَّعة</h2>\n<p>قد تستدعي React دالة الـ component مرات كثيرة: مع كل تحديث، ومرتين تحت StrictMode، وأحيانًا حين تجهّز شاشة في الخلفية. لذلك يجب أن يتصرّف الـ component كالمعادلة الرياضية: مع المدخلات نفسها يُرجع الـ JSX نفسه، ولا يغيّر شيئًا خارجه أثناء الرسم. لا تعدّل متغيّرات عامة، ولا تجلب بيانات، ولا تلمس الـ DOM مباشرة داخل جسم الدالة. ستتعلّم أين يوضع هذا النوع من العمل (الـ event handlers والـ effects) في القسم 3.</p>\n<p>إليك الفرق عمليًا. الـ component الذي ينفّذ <code dir=\"ltr\">visits += 1</code> على متغيّر في مستوى الـ module سيعطي أرقامًا مختلفة في التطوير والإنتاج، لأن StrictMode يستدعيه مرتين. أمّا الـ component الذي يكتفي بقراءة مدخلاته وإرجاع JSX فيعطي الجواب نفسه مهما استدعته React.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">أرجِع جذرًا واحدًا</h2>\n<p>يُرجع الـ component عنصر JSX واحدًا. لإرجاع عناصر متجاورة من دون إضافة <code dir=\"ltr\">&lt;div&gt;</code> زائد، غلّفها بـ <strong>fragment</strong>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">JSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> App</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">MovieCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>الـ fragment يجمع العناصر في كودك لكنه لا يضيف شيئًا إلى الـ DOM. استخدم عنصرًا حقيقيًا مثل <code dir=\"ltr\">&lt;main&gt;</code> أو <code dir=\"ltr\">&lt;section&gt;</code> حين يكون له معنى في بنية الصفحة، واستخدم الـ fragment حين لا يكون.</p>\n<p>صار بإمكانك تقسيم الشاشة إلى قطع مسمّاة. في الدرس التالي تتعلّم قواعد JSX نفسه: لماذا نكتب <code dir=\"ltr\">className</code> لا <code dir=\"ltr\">class</code>، وكيف تعمل الأقواس المعقوصة، وإلى ماذا يتحوّل JSX حين يترجمه Vite.</p>\n","toc":[{"id":"sec-1","text":"الـ component مجرّد دالة","level":2},{"id":"sec-2","text":"components داخل components","level":2},{"id":"sec-3","text":"component واحد لكل ملف","level":2},{"id":"sec-4","text":"الـ components يجب أن تكون متوقَّعة","level":2},{"id":"sec-5","text":"أرجِع جذرًا واحدًا","level":2}],"takeaways":["الـ component دالة JavaScript يبدأ اسمها بحرف كبير وتُرجع JSX.","تستخدم الـ component كأنه وسم HTML، مثل <code dir=\"ltr\">&lt;MovieCard /&gt;</code>، وReact تستدعي الدالة نيابةً عنك.","امنح كل component قابل لإعادة الاستخدام ملفًا خاصًا به وصدّره، ثم استورده حيثما تحتاجه.","عرّف الـ components في المستوى الأعلى من الـ module، ولا تعرّفها أبدًا داخل component آخر."],"further":[{"title":"Your First Component","url":"https://react.dev/learn/your-first-component"},{"title":"Importing and Exporting Components","url":"https://react.dev/learn/importing-and-exporting-components"}],"quiz":[{"q":"<p>كتبتَ هذا الـ component واستخدمته بالشكل <code dir=\"ltr\">&lt;movieCard /&gt;</code>. لم يظهر شيء، وظهر في الـ console تحذير عن وسم غير معروف. لماذا؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> movieCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Arrival&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n","options":[{"text":"يجب أن تُكتب الـ components على شكل arrow functions، لا بالكلمة المفتاحية <code dir=\"ltr\">function</code>.","why":"تعمل الـ function declarations والـ arrow functions كلتاهما كـ components. الكلمة المفتاحية ليست المشكلة."},{"text":"نسي الـ component أن يستدعي <code dir=\"ltr\">createRoot</code>.","why":"تستدعي <code dir=\"ltr\">createRoot</code> مرة واحدة في main.jsx للتطبيق كله، لا داخل كل component."},{"text":"الوسوم ذات الحرف الصغير في JSX تُعامَل كعناصر HTML، فحاولت React إنشاء عنصر DOM اسمه <code dir=\"ltr\">&lt;moviecard&gt;</code>.","why":"صحيح. لا تستدعي React دالتك إلا إذا بدأ الوسم بحرف كبير. غيّر الاسم إلى <code dir=\"ltr\">MovieCard</code>."},{"text":"لا يمكن أن يكون <code dir=\"ltr\">&lt;article&gt;</code> العنصر الأعلى الذي يُرجعه الـ component.","why":"أي عنصر واحد، بما فيه <code dir=\"ltr\">&lt;article&gt;</code>، يمكن أن يكون جذر الـ JSX الذي يُرجعه الـ component."}],"answer":2},{"q":"<p>أيّ سطر يستورد بشكل صحيح component يصدّره الملف <code dir=\"ltr\">src/components/MovieCard.jsx</code> بالشكل <code dir=\"ltr\">export default function MovieCard()</code>؟</p>","options":[{"text":"<code dir=\"ltr\">import MovieCard from './components/MovieCard.jsx';</code>","why":"صحيح. الـ default export يُستورد بلا أقواس معقوصة، ويمكنك تسميته بأي اسم (والالتزام بالاسم نفسه أحكم)."},{"text":"<code dir=\"ltr\">import { MovieCard } from './components/MovieCard.jsx';</code>","why":"الأقواس المعقوصة تستورد named export. هذا الملف لا يحتوي إلا على default export، فتكون القيمة المستوردة <code dir=\"ltr\">undefined</code>."},{"text":"<code dir=\"ltr\">import MovieCard from 'MovieCard';</code>","why":"الاسم المجرّد مثل ‘MovieCard’ يُبحث عنه في node_modules. ملفاتك الخاصة تحتاج إلى مسار نسبي."}],"answer":0},{"q":"<p>عرّف زميلك <code dir=\"ltr\">Badge</code> داخل <code dir=\"ltr\">MovieCard</code>. ما المشكلة التي يسبّبها ذلك؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> MovieCard</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Badge</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>New&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Badge</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> />&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n","options":[{"text":"إنه خطأ في الصياغة؛ لا يمكن تداخل الدوال في ملفات JSX.","why":"الدوال المتداخلة كود JavaScript صحيح. الكود يعمل، والمشكلة تظهر أثناء التشغيل."},{"text":"لا يمكن تصدير <code dir=\"ltr\">Badge</code>، فلا تستطيع الاختبارات استيراده.","why":"هذا عيب ثانوي. المشكلة الحقيقية هي ما يحدث لـ Badge في كل render."},{"text":"ترسم React الـ component المسمّى <code dir=\"ltr\">Badge</code> مرتين، مرة لكل دالة.","why":"لا شيء يُرسم مرتين هنا. الثمن الحقيقي أن Badge يُعاد إنشاؤه من الصفر."},{"text":"كل render لـ <code dir=\"ltr\">MovieCard</code> ينشئ دالة <code dir=\"ltr\">Badge</code> جديدة تمامًا، فتتخلّص React من الـ DOM والـ state الخاصين بـ Badge وتعيد بناءهما في كل مرة.","why":"صحيح. ترى React نوع component مختلفًا في كل render فتعيد تركيبه. انقل <code dir=\"ltr\">Badge</code> إلى المستوى الأعلى من الـ module."}],"answer":3},{"q":"<p>يُرجع App العنصرين <code dir=\"ltr\">&lt;Header /&gt;&lt;MovieCard /&gt;</code> متجاورين، فيعرض Vite الرسالة «Adjacent JSX elements must be wrapped in an enclosing tag». ما أصغر إصلاح صحيح؟</p>","options":[{"text":"أرجِع مصفوفة تضمّ الـ componentَين.","why":"المصفوفة تُعرض فعلًا، لكن React تُظهر تحذيرًا ما لم يكن لكل عنصر key، وهي غير مريحة للـ markup الثابت. هناك أداة أنظف لهذا."},{"text":"غلّفهما بـ fragment بالشكل <code dir=\"ltr\">&lt;&gt;…&lt;/&gt;</code>، أو بعنصر له معنى مثل <code dir=\"ltr\">&lt;main&gt;</code>.","why":"صحيح. الـ component يُرجع جذرًا واحدًا، والـ fragment يجمع العناصر من دون إضافة عقدة إلى الـ DOM."},{"text":"استدعِ كل component كدالة، <code dir=\"ltr\">Header()</code> و<code dir=\"ltr\">MovieCard()</code>، واجمع النتائج.","why":"استدعاء الـ components كدوال يتجاوز آلية الرسم في React ويكسر الـ hooks بداخلها. استخدم وسوم JSX دائمًا."}],"answer":1}],"exercise":{"kind":"web","mode":"react","starter":{"jsx":"function Header() {\n  // Return a <header> with an <h1> and a <p>\n  return null;\n}\n\nfunction MovieCard() {\n  // Return an <article> with an <h2> title and a <p> year\n  return null;\n}\n\nexport default function App() {\n  return (\n    <main>\n      {/* Use Header once and MovieCard twice */}\n    </main>\n  );\n}\n"},"solution":{"jsx":"function Header() {\n  return (\n    <header>\n      <h1>My Watchlist</h1>\n      <p>Movies I want to see</p>\n    </header>\n  );\n}\n\nfunction MovieCard() {\n  return (\n    <article>\n      <h2>Arrival</h2>\n      <p>2016</p>\n    </article>\n  );\n}\n\nexport default function App() {\n  return (\n    <main>\n      <Header />\n      <MovieCard />\n      <MovieCard />\n    </main>\n  );\n}\n"},"checks":[{"id":"header-heading","test":"return text('header h1') === 'My Watchlist';"},{"id":"header-tagline","test":"return text('header p').length > 0;"},{"id":"two-cards","test":"return $$('main article').length === 2;"},{"id":"card-content","test":"return $$('article').every((a) => a.querySelector('h2') && a.querySelector('h2').textContent.includes('Arrival') && a.querySelector('p') && a.querySelector('p').textContent.includes('2016')) && $$('article').length > 0;"}],"title":"الترويسة وبطاقات الأفلام","prompt":"<p>ابنِ الشريحة الأولى من شاشة Watchlist.</p>\n<ol>\n<li>اجعل <code dir=\"ltr\">Header</code> يُرجع <code dir=\"ltr\">&lt;header&gt;</code> يحتوي على <code dir=\"ltr\">&lt;h1&gt;</code> بالنص <strong>My Watchlist</strong> وعلى <code dir=\"ltr\">&lt;p&gt;</code> بشعار من اختيارك.</li>\n<li>اجعل <code dir=\"ltr\">MovieCard</code> يُرجع <code dir=\"ltr\">&lt;article&gt;</code> فيه <code dir=\"ltr\">&lt;h2&gt;</code> بالنص <strong>Arrival</strong> و<code dir=\"ltr\">&lt;p&gt;</code> بالنص <strong>2016</strong>.</li>\n<li>في <code dir=\"ltr\">App</code>، اعرض <code dir=\"ltr\">Header</code> مرة واحدة و<code dir=\"ltr\">MovieCard</code> مرتين داخل <code dir=\"ltr\">&lt;main&gt;</code>.</li>\n</ol>\n","hints":["الـ component يُرجع JSX، مثلًا <code dir=\"ltr\">return (&lt;header&gt;…&lt;/header&gt;);</code>. أبقِ الأقواس حتى لا تنقطع جملة الـ return.","استخدم الـ components كوسوم تبدأ بحرف كبير: <code dir=\"ltr\">&lt;Header /&gt;</code> و<code dir=\"ltr\">&lt;MovieCard /&gt;</code>.","يجب أن يحتوي App على <code dir=\"ltr\">&lt;Header /&gt;</code> و<code dir=\"ltr\">&lt;MovieCard /&gt;</code> و<code dir=\"ltr\">&lt;MovieCard /&gt;</code> داخل <code dir=\"ltr\">&lt;main&gt;</code>."],"explanation":"<p>كل component دالة في المستوى الأعلى تُرجع عنصرًا جذرًا واحدًا. ويركّبها <code dir=\"ltr\">App</code> كوسوم، فتستدعي React الدالة <code dir=\"ltr\">Header</code> مرة و<code dir=\"ltr\">MovieCard</code> مرتين، فينتج عنصرا <code dir=\"ltr\">&lt;article&gt;</code> متطابقان. هذا التكرار مقصود: في القسم 2 ستمرّر لكل بطاقة فيلمها الخاص عبر الـ props.</p>\n","checkLabels":{"header-heading":"يحتوي <header> على <h1> بالنص “My Watchlist”","header-tagline":"يحتوي <header> على <p> فيه شعار","two-cards":"يحتوي <main> على بطاقتين <article> بالضبط","card-content":"كل بطاقة فيها <h2> بالنص “Arrival” و<p> بالنص “2016”"}},"words":611,"readMinutes":4}