{"id":"l-rf-2-5","courseId":"react-fundamentals","lang":"ar","kind":"lesson","summary":"اعرض القوائم من المصفوفات باستخدام map، وصفِّها ورتّبها من دون تعديل البيانات، واختر keys تحافظ على هوية كل عنصر وعلى الـ state الخاص به بين مرّات الـ render.","html":"<p>Watchlist قائمة. وكذلك صفحة المنشورات، وجدول الطلبات، ونتائج البحث، والقائمة الرئيسية. أغلب الواجهات قوائم من أشياء مبنية من مصفوفات بيانات، ونهج React هو ما قد تخمّنه من JavaScript نفسها: حوّل مصفوفة البيانات إلى مصفوفة عناصر.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">من البيانات إلى العناصر باستخدام <code dir=\"ltr\">map</code></h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">src/components/MovieList.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\"> MovieList</span><span style=\"color:#953800;--shiki-dark:#FFA657\">({ movies }) </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\">ul</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      {</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movies.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">movie</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></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\">li</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> key</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">          {</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.title</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.year</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\">li</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      ))</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">ul</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>تُرجع <code dir=\"ltr\">map</code> مصفوفة من عناصر <code dir=\"ltr\">&lt;li&gt;</code>، وتعرض React المصفوفات بعرض كل عنصر فيها بالترتيب. لا توجد صياغة حلقات خاصة لتتعلّمها؛ إنها دالة المصفوفات التي تعرفها أصلًا.</p>\n<p>والتصفية والترتيب الفكرة نفسها: جهّز المصفوفة، ثم طبّق عليها map. افعل ذلك أثناء الـ render، من البيانات التي لديك. القائمة الظاهرة مشتقّة، فلا تحتاج إلى أن تكون state.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"1\" 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\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> movies</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { id: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'a1'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Arrival'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, year: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2016</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, watched: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { id: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'p2'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Past Lives'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, year: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2023</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, watched: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { id: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'d3'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Dune: Part Two'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, year: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2024</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, watched: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> toWatch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> movies</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">filter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">m</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> !</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">m.watched)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toSorted</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.year </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a.year);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(toWatch.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">m</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> m.title)); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// [\"Dune: Part Two\", \"Past Lives\"]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(movies[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].title);             </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Arrival (the original order is untouched)</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>تُرجع <code dir=\"ltr\">toSorted</code> نسخة مرتّبة، وهي مدعومة في كل المتصفّحات الحالية. أمّا <code dir=\"ltr\">sort</code> الأقدم فتعيد ترتيب المصفوفة <strong>في مكانها</strong>. وإذا استُدعيت على state أو props، فهي تعدّل بصمت بيانات لا تملكها. وإن احتجت إلى دعم متصفّحات أقدم، فانسخ أولًا: <code dir=\"ltr\">[...movies].sort(…)</code>.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">ما وظيفة الـ keys</h2>\n<p>كل عنصر في قائمة يحتاج إلى prop اسمه <code dir=\"ltr\">key</code>، وتحذّرك React في الـ console حين يغيب. ويتّضح السبب حين تتغيّر القائمة.</p>\n<p>حين تعيد React رسم قائمة، تكون لديها مصفوفة العناصر القديمة والجديدة. وعليها أن تطابق بينهما: أي عنصر جديد هو نفسه أي عنصر قديم؟ هذا مهمّ لأن كل عنصر قد يملك <strong>state</strong>، مثل ملاحظة نصف مكتوبة في حقل، أو زر عليه التركيز، أو قائمة مفتوحة، ويجب أن يبقى هذا الـ state مع العنصر الصحيح.</p>\n<p>الـ key هو بطاقة اسم العنصر. تطابق React العناصر القديمة والجديدة حسب الـ key. الـ key نفسه: إنه العنصر نفسه، فتحتفظ React بالـ DOM والـ state الخاصين به وتحدّث ما تغيّر. key جديد: عنصر جديد، فتُنشئه React. key غائب: أُزيل العنصر، فتهدمه React.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 290\" role=\"img\" aria-labelledby=\"l-rf-2-5-t1\"><defs><marker id=\"arrow-l-rf-2-5-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-rf-2-5-t1\">قبل: لدى Arrival ملاحظة. بعد إضافة Dune في الأعلى مع keys الـ index، صار الـ key صفر يشير إلى Dune، فتقفز الملاحظة إلى Dune. ومع keys المعرّف، تبقى الملاحظة مع Arrival.</title>\n  <text class=\"d-label-strong\" x=\"110\" y=\"24\" text-anchor=\"middle\">قبل</text>\n  <rect class=\"d-box\" x=\"20\" y=\"40\" width=\"180\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"110\" y=\"65\" text-anchor=\"middle\">0 · Arrival · \"Loved it\"</text>\n  <rect class=\"d-box\" x=\"20\" y=\"90\" width=\"180\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"110\" y=\"115\" text-anchor=\"middle\">1 · Past Lives</text>\n  <text class=\"d-label-strong\" x=\"350\" y=\"24\" text-anchor=\"middle\">key={index}</text>\n  <rect class=\"d-box-warn\" x=\"255\" y=\"40\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"350\" y=\"65\" text-anchor=\"middle\">0 · Dune · \"Loved it\"</text>\n  <rect class=\"d-box\" x=\"255\" y=\"90\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"350\" y=\"115\" text-anchor=\"middle\">1 · Arrival</text>\n  <rect class=\"d-box\" x=\"255\" y=\"140\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"350\" y=\"165\" text-anchor=\"middle\">2 · Past Lives</text>\n  <text class=\"d-label-strong\" x=\"590\" y=\"24\" text-anchor=\"middle\">key={movie.id}</text>\n  <rect class=\"d-box\" x=\"495\" y=\"40\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"590\" y=\"65\" text-anchor=\"middle\">d3 · Dune</text>\n  <rect class=\"d-box-success\" x=\"495\" y=\"90\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"590\" y=\"115\" text-anchor=\"middle\">a1 · Arrival · \"Loved it\"</text>\n  <rect class=\"d-box\" x=\"495\" y=\"140\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"590\" y=\"165\" text-anchor=\"middle\">p2 · Past Lives</text>\n  <path class=\"d-arrow\" d=\"M200 60 L253 60\" marker-end=\"url(#arrow-l-rf-2-5-1)\"/>\n  <path class=\"d-arrow d-dashed\" d=\"M200 60 C 330 220, 420 120, 493 110\" marker-end=\"url(#arrow-l-rf-2-5-1)\"/>\n  <text class=\"d-label-muted\" x=\"350\" y=\"230\" text-anchor=\"middle\">الـ key نفسه يعني الصف نفسه: الـ state يتبع الـ key</text>\n  <text class=\"d-label-muted\" x=\"350\" y=\"256\" text-anchor=\"middle\">keys الـ index تتبع الموضع، وkeys المعرّف تتبع الفيلم</text>\n</svg><figcaption>keys الـ index مقابل keys المعرّف بعد الإدراج في الأعلى</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">اختيار الـ key</h2>\n<p>الـ key الجيد <strong>فريد بين الإخوة</strong> و<strong>ثابت</strong>: للعنصر نفسه الـ key نفسه في كل render.</p>\n<ul>\n<li><strong>استخدم معرّفًا من بياناتك.</strong> صفوف قاعدة البيانات وكائنات الـ API لها معرّف أصلًا.</li>\n<li><strong>أنشئ المعرّفات حين تُنشأ العناصر.</strong> للأفلام المضافة عبر نموذج، ولّد المعرّف في المعالج الذي يضيفها: <code dir=\"ltr\">{ id: crypto.randomUUID(), title, year }</code>. يُخزَّن على الكائن ولا يتغيّر أبدًا.</li>\n<li><strong>لا تولّد الـ keys أثناء الـ render أبدًا.</strong> <code dir=\"ltr\">key={Math.random()}</code> أو <code dir=\"ltr\">key={crypto.randomUUID()}</code> داخل <code dir=\"ltr\">map</code> ينتج keys جديدة في كل render. فتعامل React كل صف على أنه جديد تمامًا، وتهدم الـ DOM، وتُضيّع تركيز الحقل والـ state مع كل حرف تكتبه.</li>\n<li><strong>لا تستخدم محتوى غير فريد.</strong> العناوين تتكرّر؛ هناك عدّة أفلام اسمها “Dune”.</li>\n</ul>\n<p>لا يلزم أن تكون الـ keys فريدة إلا بين الإخوة في القائمة نفسها. ويمكن لقائمتين مختلفتين استخدام المعرّفات نفسها.</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>الـ index كـ key في قائمة تتغيّر</span></p>\n<p>الكود <code dir=\"ltr\">movies.map((movie, index) =&gt; &lt;li key={index}&gt;…)</code> يُسكت التحذير ويعمل حتى يتغيّر ترتيب القائمة. أدرج في الأعلى، أو احذف من المنتصف، أو رتّب، أو صفِّ، وستطابق React الصفوف حسب الموضع. يبقى الـ state والتركيز وقيم الحقول غير المتحكَّم بها في الموضع نفسه بينما تتحرّك البيانات. keys الـ index آمنة فقط للقوائم الثابتة التي لا يتغيّر ترتيبها أبدًا، مثل مجموعة ثابتة من روابط التذييل.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">الـ keys على الـ components والـ fragments</h2>\n<p>يوضع الـ key على العنصر الخارجي الذي تُرجعه دالة الـ callback في <code dir=\"ltr\">map</code>. وحين يكون ذلك component، ضعه على الـ component:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"2\"><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:#1F2328;--shiki-dark:#E6EDF3\">{movies.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">movie</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></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\">MovieItem</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> key</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> movie</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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<p>لا يستطيع <code dir=\"ltr\">MovieItem</code> قراءة <code dir=\"ltr\">key</code>؛ فـ React تستخدمه ولا تمرّره إلى الأسفل. وإذا احتاج الـ component إلى المعرّف، فمرّره بشكل منفصل كـ prop.</p>\n<p>وحين يرسم كل عنصر عدّة إخوة من دون غلاف، استخدم الصيغة الطويلة للـ fragment، فهي تقبل key:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"3\"><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\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { Fragment } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{movies.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">movie</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></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\">Fragment</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> key</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\">dt</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.title</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">dt</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\">dd</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.year</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">dd</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\">Fragment</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<p>الصيغة القصيرة <code dir=\"ltr\">&lt;&gt;…&lt;/&gt;</code> لا تقبل key.</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>يمكن للـ keys أن تعيد ضبط component عمدًا</span></p>\n<p>لأن الـ key الجديد يعني component جديدًا، فإن تغيير الـ key يجبر React على البدء من جديد. <code dir=\"ltr\">&lt;MovieDetails key={selectedId} … /&gt;</code> يعطي كل فيلم مختار لوحة تفاصيل جديدة بـ state جديد. إنها حيلة مفيدة ستراها مجددًا.</p>\n</aside>\n<p>صار بإمكانك عرض أي قائمة بشكل صحيح. وبهذا يكتمل Watchlist الثابت. في القسم التالي يبدأ بالحركة: الـ state يتيح للقائمة أن تتغيّر حين ينقر المستخدم ويكتب ويرسل.</p>\n","toc":[{"id":"sec-1","text":"من البيانات إلى العناصر باستخدام map","level":2},{"id":"sec-2","text":"ما وظيفة الـ keys","level":2},{"id":"sec-3","text":"اختيار الـ key","level":2},{"id":"sec-4","text":"الـ keys على الـ components والـ fragments","level":2}],"takeaways":["حوّل مصفوفة البيانات إلى مصفوفة عناصر باستخدام <code dir=\"ltr\">map</code>، وضع الـ <code dir=\"ltr\">key</code> على العنصر الخارجي الذي تُرجعه دالة الـ callback.","يُخبر الـ key React بهوية كل عنصر بين مرّات الـ render، لذا يجب أن يكون فريدًا بين الإخوة وثابتًا مع الوقت.","استخدم معرّفًا (id) من بياناتك كـ key؛ وأنشئ المعرّفات لحظة إنشاء العناصر، لا أثناء الـ render أبدًا.","الـ keys المبنية على الـ index تنكسر حين يمكن إعادة ترتيب القائمة أو تصفيتها أو الإدراج فيها، لأن الـ state يلتصق بالموضع بدل العنصر.","صفِّ نُسخًا من بياناتك ورتّبها أثناء الـ render؛ فالدالة <code dir=\"ltr\">sort</code> تغيّر المصفوفة في مكانها، لذا استخدم <code dir=\"ltr\">toSorted</code> أو رتّب نسخة."],"further":[{"title":"Rendering Lists","url":"https://react.dev/learn/rendering-lists"},{"title":"Preserving and Resetting State","url":"https://react.dev/learn/preserving-and-resetting-state"},{"title":"Array.prototype.toSorted() on MDN","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted"}],"quiz":[{"q":"<p>أين يوضع الـ key؟</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\"> MovieList</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">movies</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\">ul</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      {</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movies.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">movie</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></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\">MovieItem</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> movie</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      ))</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">ul</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","options":[{"text":"على الـ <code dir=\"ltr\">&lt;li&gt;</code> داخل MovieItem، بالشكل <code dir=\"ltr\">&lt;li key={movie.id}&gt;</code>.","why":"تحتاج React إلى الـ keys على العناصر في المصفوفة التي تُرجعها من <code dir=\"ltr\">map</code>. داخل MovieItem لا يوجد إلا <code dir=\"ltr\">&lt;li&gt;</code> واحد، فالـ key هناك لا يفعل شيئًا."},{"text":"على الـ <code dir=\"ltr\">&lt;ul&gt;</code>، بالشكل <code dir=\"ltr\">&lt;ul key=&quot;movies&quot;&gt;</code>.","why":"الـ <code dir=\"ltr\">&lt;ul&gt;</code> ليس واحدًا من إخوة كثيرين. العناصر بداخله هي ما يجب على React التمييز بينها."},{"text":"على MovieItem نفسه، بالشكل <code dir=\"ltr\">&lt;MovieItem key={movie.id} movie={movie} /&gt;</code>.","why":"صحيح. يوضع الـ key على العنصر الخارجي الذي تُرجعه دالة الـ callback في <code dir=\"ltr\">map</code>، وهو هنا الـ component."},{"text":"لا مكان له، لأن الـ components تحصل على keys تلقائية.","why":"ترجع React إلى الـ index وتحذّر أثناء التطوير. وهذا البديل بالضبط هو ما يسبّب الأخطاء حين تتغيّر القائمة."}],"answer":2},{"q":"<p>لكل صف فيلم حقل نصي لملاحظة. كتبت «Loved it» بجانب Arrival، ثم أضفت فيلمًا جديدًا في أعلى القائمة. مع <code dir=\"ltr\">key={index}</code>، أين تنتهي «Loved it»؟</p>","options":[{"text":"تبقى بجانب Arrival.","why":"هذا ما ستحصل عليه مع <code dir=\"ltr\">key={movie.id}</code>. أمّا مع keys الـ index فتطابق React الصفوف حسب الموضع، لا حسب الفيلم."},{"text":"بجانب الفيلم الجديد في الأعلى، لأن له الآن الـ index صفر، وهو الـ key الذي كان لصف Arrival.","why":"صحيح. تعيد React استخدام الصف ذي الـ key صفر، بما فيه حالة الـ DOM في حقله، لأي عنصر صار الآن في الموضع صفر."},{"text":"تختفي، لأن كل الصفوف يُعاد إنشاؤها.","why":"لا تعيد React إنشاء الصفوف التي ما زالت keys الخاصة بها موجودة، بل تعيد استخدامها، ولهذا بالضبط تنتقل الملاحظة."},{"text":"ترمي React خطأ بسبب تكرار الـ key.","why":"الـ indexes فريدة، فلا يوجد تكرار. الخطأ صامت، وهذا ما يجعله خطيرًا."}],"answer":1},{"q":"<p>أيّ key اختيار جيد للأفلام التي يضيفها المستخدم من خلال نموذج؟</p>","options":[{"text":"<code dir=\"ltr\">key={Math.random()}</code> داخل دالة الـ callback في map.","why":"الـ key الجديد في كل render يجعل React تهدم كل صف وتعيد إنشاءه في كل مرة، فيضيع التركيز والـ state."},{"text":"<code dir=\"ltr\">key={movie.title}</code>","why":"العناوين ليست فريدة؛ هناك عدّة أفلام اسمها “Dune”. والأخوان اللذان لهما الـ key نفسه يربكان React."},{"text":"<code dir=\"ltr\">key={index}</code>، لأن المستخدم يضيف أفلامًا فقط.","why":"سيحذف المستخدمون ويصفّون ويرتّبون أيضًا. وتنكسر keys الـ index بمجرّد أن تتحرّك العناصر نسبةً إلى بعضها."},{"text":"معرّف يُنشأ مرة واحدة بـ <code dir=\"ltr\">crypto.randomUUID()</code> عند إضافة الفيلم، ويُخزَّن على كائن الفيلم.","why":"صحيح. يُنشأ المعرّف مع البيانات، ويبقى كما هو طوال عمر الفيلم، وهو فريد."}],"answer":3},{"q":"<p>تأتي <code dir=\"ltr\">movies</code> من الـ state. ما المشكلة في <code dir=\"ltr\">const sorted = movies.sort((a, b) =&gt; b.year - a.year);</code> داخل كود الـ render؟</p>","options":[{"text":"الدالة <code dir=\"ltr\">sort</code> تعيد ترتيب <code dir=\"ltr\">movies</code> في مكانها، فأنت تعدّل الـ state أثناء الـ render.","why":"صحيح. استخدم <code dir=\"ltr\">movies.toSorted(…)</code> أو <code dir=\"ltr\">[...movies].sort(…)</code> لترتيب نسخة وترك الـ state كما هو."},{"text":"الدالة <code dir=\"ltr\">sort</code> لا تستطيع مقارنة الأرقام.","why":"مع دالة مقارنة مثل <code dir=\"ltr\">(a, b) =&gt; b.year - a.year</code>، ترتّب sort الأرقام بشكل صحيح."},{"text":"يجب أن يحدث الترتيب داخل effect، لا أثناء الـ render.","why":"حساب قيمة مشتقّة أثناء الـ render هو الصواب تمامًا. المشكلة في تغيير المصفوفة الأصلية."}],"answer":0}],"exercise":{"kind":"web","mode":"react","starter":{"jsx":"import { useState } from 'react';\n\nconst INITIAL = [\n  { id: 'a1', title: 'Arrival', year: 2016 },\n  { id: 'p2', title: 'Past Lives', year: 2023 },\n];\n\nexport default function App() {\n  const [movies, setMovies] = useState(INITIAL);\n\n  function addDune() {\n    setMovies([{ id: 'd3', title: 'Dune: Part Two', year: 2024 }, ...movies]);\n  }\n\n  // 1. Show the movies newest first, without changing `movies` itself.\n  // 2. Fix the key so each note stays with its movie.\n  return (\n    <main>\n      <button onClick={addDune}>Add Dune: Part Two</button>\n      <ul>\n        {movies.map((movie, index) => (\n          <li key={index}>\n            <span>{movie.title} ({movie.year})</span>\n            <input aria-label=\"Note\" placeholder=\"Note\" />\n          </li>\n        ))}\n      </ul>\n    </main>\n  );\n}\n"},"solution":{"jsx":"import { useState } from 'react';\n\nconst INITIAL = [\n  { id: 'a1', title: 'Arrival', year: 2016 },\n  { id: 'p2', title: 'Past Lives', year: 2023 },\n];\n\nexport default function App() {\n  const [movies, setMovies] = useState(INITIAL);\n\n  function addDune() {\n    setMovies([{ id: 'd3', title: 'Dune: Part Two', year: 2024 }, ...movies]);\n  }\n\n  const newestFirst = movies.toSorted((a, b) => b.year - a.year);\n\n  return (\n    <main>\n      <button onClick={addDune}>Add Dune: Part Two</button>\n      <ul>\n        {newestFirst.map((movie) => (\n          <li key={movie.id}>\n            <span>{movie.title} ({movie.year})</span>\n            <input aria-label=\"Note\" placeholder=\"Note\" />\n          </li>\n        ))}\n      </ul>\n    </main>\n  );\n}\n"},"checks":[{"id":"sorted","test":"const titles = $$('li').map((li) => text(li.querySelector('span')).replace(/\\s*\\(\\d+\\)$/, '')); return titles.join('|') === 'Past Lives|Arrival';"},{"id":"note-follows-movie","test":"const row = (t) => $$('li').find((li) => text(li.querySelector('span')).startsWith(t)); await input(row('Arrival').querySelector('input'), 'Loved it'); await click($('button')); const a = row('Arrival'); const d = row('Dune'); return !!a && !!d && a.querySelector('input').value === 'Loved it' && d.querySelector('input').value === '';"},{"id":"sorted-after-add","test":"const titles = $$('li').map((li) => text(li.querySelector('span')).replace(/\\s*\\(\\d+\\)$/, '')); return titles.join('|') === 'Dune: Part Two|Past Lives|Arrival';"}],"title":"keys تُبقي الملاحظات في مكانها","prompt":"<p>لكل صف فيلم حقل لملاحظة. هناك مشكلتان:</p>\n<ol>\n<li>يجب أن تعرض القائمة الأفلام <strong>من الأحدث إلى الأقدم</strong> (حسب <code dir=\"ltr\">year</code>). رتّب نسخة أثناء الـ render، ولا تغيّر الـ state المسمّى <code dir=\"ltr\">movies</code>.</li>\n<li>اكتب ملاحظة بجانب Arrival، ثم انقر <strong>Add Dune: Part Two</strong>. تقفز الملاحظة إلى الفيلم الخطأ. أصلح الـ key حتى تبقى كل ملاحظة مع فيلمها.</li>\n</ol>\n<p>أبقِ الـ markup كما هو: <code dir=\"ltr\">&lt;li&gt;</code> واحد لكل فيلم، فيه <code dir=\"ltr\">&lt;span&gt;</code> (العنوان والسنة) و<code dir=\"ltr\">&lt;input&gt;</code>.</p>\n","hints":["<code dir=\"ltr\">movies.toSorted((a, b) =&gt; b.year - a.year)</code> تُرجع مصفوفة جديدة مرتّبة وتترك <code dir=\"ltr\">movies</code> كما هي.","طبّق map على النسخة المرتّبة، لا على <code dir=\"ltr\">movies</code>.","لكل فيلم <code dir=\"ltr\">id</code> ثابت أصلًا. استخدم <code dir=\"ltr\">key={movie.id}</code> بدلًا من الـ index."],"explanation":"<p>مع <code dir=\"ltr\">key={index}</code> تطابق React الصفوف حسب الموضع. إضافة فيلم تُزيح كل شيء إلى الأسفل، فيُعاد استخدام الصف الذي كان لـ Arrival (الـ key صفر في كود البداية) لأي فيلم صار في ذلك الموضع الآن، والملاحظة ما زالت في حقله. أمّا مع <code dir=\"ltr\">key={movie.id}</code> فتعرف React أن صف Arrival هو العنصر نفسه كما كان، فتنقله بقيمة حقله وكل شيء. وترتيب نسخة أثناء الـ render يُبقي <code dir=\"ltr\">movies</code> المصدر الوحيد للحقيقة؛ والترتيب من الأحدث إلى الأقدم يُشتقّ منها في كل مرة.</p>\n","checkLabels":{"sorted":"تظهر الأفلام من الأحدث إلى الأقدم: Past Lives ثم Arrival","note-follows-movie":"بعد إضافة Dune، ما زالت ملاحظة Arrival بجانب Arrival","sorted-after-add":"يظهر Dune: Part Two (2024) أولًا بعد إضافته"}},"words":614,"readMinutes":4}