{"id":"l-rf-2-1","courseId":"react-fundamentals","lang":"ar","kind":"lesson","summary":"حوّل تصميم الشاشة إلى شجرة components، وابنِ نسخة ثابتة أولًا، وحدّد أقل قدر من الـ state يحتاجه Watchlist قبل كتابة أي كود تفاعلي.","html":"<p>هذا هو Watchlist الذي تبنيه، موصوفًا كما يسلّمه المصمّم: ترويسة فيها العنوان وعبارة «3 to watch»؛ ونموذج فيه حقل لعنوان الفيلم وزر Add؛ وصف من أزرار الفلترة (All وTo watch وWatched)؛ وقائمة أفلام، لكل فيلم عنوانه وسنته ومربّع اختيار لتعليمه كمُشاهَد. وحين تكون القائمة فارغة، تحلّ محلّها رسالة ودودة.</p>\n<p>قبل أن تكتب سطرًا واحدًا من الكود، قرّر ما هي الـ components. هذه أهم خطوة تصميمية في تطبيق React، وتُنجَز بالقلم.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">ارسم صناديق وسمِّها</h2>\n<p>خذ التصميم وارسم صندوقًا حول كل قطعة تؤدّي عملًا واحدًا. ثم سمِّ كل صندوق باسم، كما تسمّي دالة.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 300\" role=\"img\" aria-labelledby=\"l-rf-2-1-t1\"><defs><marker id=\"arrow-l-rf-2-1-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-1-t1\">App هو الجذر، وأبناؤه Header وAddMovieForm وFilterBar وMovieList. يحتوي Header على WatchCount. ويحتوي MovieList على عدّة components من نوع MovieItem، أو على EmptyState حين لا توجد أفلام.</title>\n  <rect class=\"d-box-primary\" x=\"290\" y=\"16\" width=\"120\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"44\" text-anchor=\"middle\">App</text>\n  <rect class=\"d-box\" x=\"20\" y=\"120\" width=\"130\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"85\" y=\"148\" text-anchor=\"middle\">Header</text>\n  <rect class=\"d-box\" x=\"190\" y=\"120\" width=\"150\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"265\" y=\"148\" text-anchor=\"middle\">AddMovieForm</text>\n  <rect class=\"d-box\" x=\"380\" y=\"120\" width=\"120\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"440\" y=\"148\" text-anchor=\"middle\">FilterBar</text>\n  <rect class=\"d-box-accent\" x=\"540\" y=\"120\" width=\"140\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"610\" y=\"148\" text-anchor=\"middle\">MovieList</text>\n  <rect class=\"d-box\" x=\"20\" y=\"230\" width=\"130\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"85\" y=\"258\" text-anchor=\"middle\">WatchCount</text>\n  <rect class=\"d-box\" x=\"440\" y=\"230\" width=\"120\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"500\" y=\"258\" text-anchor=\"middle\">MovieItem ×n</text>\n  <rect class=\"d-box\" x=\"575\" y=\"230\" width=\"115\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"632\" y=\"258\" text-anchor=\"middle\">EmptyState</text>\n  <path class=\"d-line\" d=\"M350 60 L85 120\"/>\n  <path class=\"d-line\" d=\"M350 60 L265 120\"/>\n  <path class=\"d-line\" d=\"M350 60 L440 120\"/>\n  <path class=\"d-line\" d=\"M350 60 L610 120\"/>\n  <path class=\"d-line\" d=\"M85 164 L85 230\"/>\n  <path class=\"d-line\" d=\"M610 164 L500 230\"/>\n  <path class=\"d-line d-dashed\" d=\"M610 164 L632 230\"/>\n</svg><figcaption>تطبيق Watchlist على شكل شجرة components</figcaption></figure>\n<p>الصناديق المتداخلة تصبح شجرة. يملك <code dir=\"ltr\">App</code> الشاشة كلها. ويرسم <code dir=\"ltr\">MovieList</code> إمّا عدّة <code dir=\"ltr\">MovieItem</code> وإمّا <code dir=\"ltr\">EmptyState</code> واحدًا. هذه الشجرة هي خطة بقية الدورة؛ ستبني كل صندوق فيها كـ component.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">متى تقسّم، ومتى لا تفعل</h2>\n<p>اجعل القطعة component مستقلًا إذا تحقّق واحد على الأقل مما يلي:</p>\n<ul>\n<li><strong>تتكرّر.</strong> يظهر <code dir=\"ltr\">MovieItem</code> مرة لكل فيلم، والغاية أن تكتبه مرة واحدة.</li>\n<li><strong>لها عمل خاص بها.</strong> يتولّى <code dir=\"ltr\">AddMovieForm</code> الكتابة والإرسال، ولا يحتاج أي جزء آخر إلى معرفة كيف يفعل ذلك.</li>\n<li><strong>الأب صار صعب القراءة.</strong> إن كنت تحتاج إلى التمرير لتفهم component، فاستخرج منه قطعة لها اسم.</li>\n</ul>\n<p>لا تقسّم لمجرد أنك تستطيع. الـ component الذي لا يفعل سوى تغليف <code dir=\"ltr\">&lt;span&gt;</code> واحد يضيف ملفًا وسطر import واسمًا عليك تذكّره، ولا يعطيك شيئًا في المقابل. الفرق المتمرّسة كثيرًا ما تُبقي component من 100 سطر بدل أن تقسّمه إلى خمسة من 20 سطرًا لا تُستخدم إلا معًا. ابدأ بحجم أكبر قليلًا، واستخرج القطع حين يظهر أحد الأسباب الثلاثة.</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>سمِّ حسب الدور، لا حسب الشكل</span></p>\n<p>الاسمان <code dir=\"ltr\">FilterBar</code> و<code dir=\"ltr\">WatchCount</code> يصفان الغرض من القطعتين. أمّا <code dir=\"ltr\">BlueButtonRow</code> و<code dir=\"ltr\">BigNumber</code> فيفقدان دقّتهما في اليوم الذي يتغيّر فيه التصميم.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">ابنِ النسخة الثابتة أولًا</h2>\n<p>بعد رسم الشجرة، ابنِها ببيانات مكتوبة يدويًا ومن دون أي تفاعل. مرّر البيانات من الأعلى إلى الأسفل حتى يكون الشكل صحيحًا من البداية:</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\">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:#0550AE;--shiki-dark:#79C0FF\">1</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:#0550AE;--shiki-dark:#79C0FF\">2</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:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Paddington 2'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, year: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2017</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\">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:#0550AE;--shiki-dark:#79C0FF\"> movies</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</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\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">AddMovieForm</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\">FilterBar</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\">MovieList</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> movies</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</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\">    &#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>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// …Header, AddMovieForm, FilterBar and MovieList defined below</span></span></code></pre></div>\n<p>بناء النسخة الثابتة كتابة في معظمه، وهو يضبط البنية والـ markup بينما لا شيء يتحرّك. أمّا إضافة التفاعل فتفكير في معظمها. والقيام بالأمرين معًا هو الطريق الذي تنتهي به الـ components متشابكة.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">اعثر على أقل قدر من الـ state</h2>\n<p>بعد ذلك، اكتب قائمة بكل قطعة بيانات في التطبيق، واطرح على كل واحدة ثلاثة أسئلة: هل تبقى ثابتة مع الوقت؟ هل تأتي من الأب؟ هل يمكنك حسابها من بيانات أخرى؟ إذا كان الجواب نعم على أيٍّ منها، فهي ليست state.</p>\n<p>بالنسبة إلى Watchlist:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>البيانات</th>\n<th>state؟</th>\n<th>السبب</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>قائمة الأفلام</td>\n<td>نعم</td>\n<td>تتغيّر حين تضيف فيلمًا أو تحذفه أو تعلّمه</td>\n</tr>\n<tr>\n<td>الفلتر الحالي</td>\n<td>نعم</td>\n<td>يتغيّر حين تنقر زر فلترة</td>\n</tr>\n<tr>\n<td>النص المكتوب في النموذج</td>\n<td>نعم</td>\n<td>يتغيّر أثناء الكتابة</td>\n</tr>\n<tr>\n<td>عدد الأفلام المتبقية</td>\n<td>لا</td>\n<td>يُحسب من الأفلام</td>\n</tr>\n<tr>\n<td>الأفلام الظاهرة حاليًا</td>\n<td>لا</td>\n<td>تُحسب من الأفلام + الفلتر</td>\n</tr>\n</tbody>\n</table></div><p>ثلاث قطع من الـ state، وقيمتان مشتقّتان. إبقاء الـ state في حدّه الأدنى هو العادة الوحيدة التي تمنع أكبر عدد من الأخطاء في تطبيقات React. فكل قطعة state إضافية نسخة ثانية من الحقيقة قد تختلف عن الأولى.</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>تخزين ما يمكنك حسابه</span></p>\n<p>كثيرًا ما يحتفظ المبتدئون بـ <code dir=\"ltr\">visibleMovies</code> في الـ state إلى جانب <code dir=\"ltr\">movies</code> و<code dir=\"ltr\">filter</code>، ثم ينسون تحديثها حين يُضاف فيلم، فتصبح القائمة على الشاشة قديمة بصمت. إذا أمكن حساب قيمة من state لديك أصلًا، فاحسبها أثناء الـ render.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">أين تعيش البيانات</h2>\n<p>يحتاج <code dir=\"ltr\">Header</code> إلى الأفلام (من أجل العدد)، ويحتاجها <code dir=\"ltr\">MovieList</code> (لعرضها). والقاعدة: تعيش البيانات في أقرب أب مشترك لكل الـ components التي تحتاجها، وهو هنا <code dir=\"ltr\">App</code>. وتنزل منه كـ props. وحين يحتاج ابنٌ إلى تغييرها، يمرّر له الأب دالة يستدعيها. ستفعل هذا تحديدًا في القسم 3.</p>\n<p>في الدرس التالي تتعلّم الـ props كما يجب: كيف تمرّر البيانات إلى الـ components، وتضبط القيم الافتراضية، وتُبقي الـ components قابلة لإعادة الاستخدام.</p>\n","toc":[{"id":"sec-1","text":"ارسم صناديق وسمِّها","level":2},{"id":"sec-2","text":"متى تقسّم، ومتى لا تفعل","level":2},{"id":"sec-3","text":"ابنِ النسخة الثابتة أولًا","level":2},{"id":"sec-4","text":"اعثر على أقل قدر من الـ state","level":2},{"id":"sec-5","text":"أين تعيش البيانات","level":2}],"takeaways":["ارسم صناديق حول التصميم وسمِّ كل صندوق؛ الصناديق المتداخلة تصبح شجرة components.","قسّم الـ component حين تتكرّر قطعة منه، أو يكون لها عمل مستقل، أو حين يصعب قراءة الأب، لا لمجرد العادة.","ابنِ نسخة ثابتة من بيانات مكتوبة يدويًا أولًا، ثم أضف التفاعل.","احتفظ بأقل قدر من الـ state؛ فكل ما يمكن حسابه من بيانات أخرى، كالعدد أو القائمة المصفّاة، قيمة مشتقّة وليس state."],"further":[{"title":"Thinking in React","url":"https://react.dev/learn/thinking-in-react"},{"title":"Your UI as a Tree","url":"https://react.dev/learn/understanding-your-ui-as-a-tree"}],"quiz":[{"q":"<p>يحتاج Watchlist إلى عرض عدد الأفلام غير المُشاهَدة. من أين يجب أن يأتي هذا الرقم؟</p>","options":[{"text":"من state منفصل اسمه <code dir=\"ltr\">unwatchedCount</code> يحدّثه كل معالج أحداث.","why":"النسخة الثانية من معلومة موجودة أصلًا في القائمة ستختلف عنها في يوم ما. وهذا بالضبط الخطأ الذي يُفترض أن تزيله React."},{"text":"يُحسب من مصفوفة الأفلام أثناء الـ render، باستخدام <code dir=\"ltr\">movies.filter((m) =&gt; !m.watched).length</code>.","why":"صحيح. إنها بيانات مشتقّة. حسابها في كل render يُبقيها صحيحة تلقائيًا، وتكلفتها معدومة مع قائمة بهذا الحجم."},{"text":"يُخزَّن في localStorage ويُقرأ من جديد في كل render.","why":"الـ localStorage لحفظ البيانات بين مرات إعادة التحميل، لا لقيم يمكنك حسابها من بيانات موجودة لديك."}],"answer":1},{"q":"<p>قسّم زميلك بطاقة الفيلم إلى <code dir=\"ltr\">MovieTitle</code> و<code dir=\"ltr\">MovieYear</code> و<code dir=\"ltr\">MovieTitleWrapper</code> و<code dir=\"ltr\">MovieYearText</code>، وكلٌّ منها يغلّف وسمًا واحدًا. ما أفضل تعليق في مراجعة الكود؟</p>","options":[{"text":"ممتاز، الأصغر دائمًا أفضل في React.","why":"الـ components الصغيرة جدًا التي لا تفعل سوى تغليف وسم واحد تضيف ملفات وطبقات من دون أن تضيف إعادة استخدام أو وضوحًا."},{"text":"استخدم class components للصغيرة منها.","why":"الـ function components هي المعيار، والاختيار بين class وfunction ليس المشكلة هنا."},{"text":"ادمجها في component واحد، لأن React لا تستطيع رسم أكثر من component واحد لكل ملف.","why":"يمكن للملف أن يعرّف ويرسم أي عدد من الـ components. المشكلة هي التقسيم غير الضروري، لا قاعدة متعلّقة بالملفات."},{"text":"أبقِ MovieCard واحدًا حتى تتكرّر قطعة منه أو يصبح لها منطقها الخاص؛ هذه التقسيمات تضيف طبقات بلا فائدة.","why":"صحيح. قسّم من أجل إعادة الاستخدام، أو لعمل مستقل واضح، أو لسهولة القراءة. تغليف الوسوم المفردة يجعل الكود أصعب في المتابعة."}],"answer":3},{"q":"<p>في شجرة Watchlist، يحتوي <code dir=\"ltr\">App</code> على <code dir=\"ltr\">Header</code> و<code dir=\"ltr\">AddMovieForm</code> و<code dir=\"ltr\">FilterBar</code> و<code dir=\"ltr\">MovieList</code>، ويحتوي <code dir=\"ltr\">MovieList</code> على كثير من <code dir=\"ltr\">MovieItem</code>. أيّ الـ components تحتاج إلى مصفوفة الأفلام؟</p>","options":[{"text":"Header (من أجل العدد) وMovieList (لعرض الأفلام)، لذا يجب أن تعيش في أبيهما المشترك App.","why":"صحيح. البيانات التي تحتاجها عدّة components تعيش في أقرب أب مشترك لها، وتنزل إليها كـ props."},{"text":"MovieItem فقط، لأنه هو من يعرض الفيلم.","why":"كل MovieItem يحتاج فيلمًا واحدًا، لكن Header يحتاج القائمة كاملة ليعدّها، وMovieList يحتاجها ليرسم العناصر."},{"text":"كل الـ components، لذا يجب أن يحتفظ كلٌّ منها بنسخته الخاصة.","why":"النسخ تتباعد مع الوقت. مالك واحد مع props تنزل منه يُبقي كل شيء متّسقًا."},{"text":"FilterBar، لأن الفلترة هي التي تحدّد ما يظهر.","why":"يحتاج FilterBar إلى قيمة الفلتر الحالية، لا إلى الأفلام. وMovieList هو من يجمع الاثنين."}],"answer":0},{"q":"<p>لماذا تبني نسخة ثابتة من Watchlist ببيانات مكتوبة يدويًا قبل إضافة الـ state؟</p>","options":[{"text":"لا تستطيع React رسم components فيها state قبل وجود نسخة ثابتة.","why":"لا توجد قاعدة كهذه. إنها طريقة عمل، لا شرط تقني."},{"text":"الـ components الثابتة تُرسم أسرع في الإنتاج.","why":"السرعة ليست السبب. ترتيب العمل يتعلّق بالفصل بين نوعين مختلفين من التفكير."},{"text":"لأنها تفصل التخطيط والبنية عن التفاعل، فتضبط الشجرة قبل أن تفكّر فيما يتغيّر.","why":"صحيح. بناء النسخة الثابتة كتابة في معظمه، أمّا تحديد الـ state فيحتاج تفكيرًا متأنّيًا. التعامل مع كلٍّ منهما على حدة يمنع تشابكهما."}],"answer":2}],"exercise":{"kind":"choice","multiple":true,"options":["ratings","average-rating","sort-order","sorted-movies","is-sorted-by-year"],"correct":["ratings","sort-order"],"title":"اعثر على أقل قدر من الـ state","prompt":"<p>يحصل Watchlist على ميزتين جديدتين. صار بإمكان كل فيلم أن يحمل تقييمًا من 1 إلى 5 نجوم تضبطه بالنقر على النجوم، وصارت هناك قائمة منسدلة تتيح ترتيب القائمة حسب <strong>العنوان</strong> أو حسب <strong>السنة</strong>. وتعرض الترويسة أيضًا متوسّط تقييم الأفلام التي شاهدتها.</p>\n<p><strong>أيٌّ من هذه يجب تخزينه في الـ state؟</strong> اختر كل ما هو state فعلًا، واترك كل ما هو مشتقّ.</p>\n","hints":["اسأل عن كل عنصر: هل يمكنني حساب هذا من بيانات أخرى موجودة لديّ؟","المتوسّط والنسخة المرتّبة والقيمة المنطقية عن نوع الترتيب كلها يمكن حسابها من التقييمات ومن الترتيب المختار."],"explanation":"<p>شيئان فقط يتغيّران من تلقاء نفسيهما بسبب ما يفعله المستخدم: تقييم كل فيلم (المخزَّن على الفيلم داخل state الأفلام) والترتيب المختار. كل ما عدا ذلك يُحسب أثناء الـ render. فالمتوسّط هو <code dir=\"ltr\">مجموع تقييمات المُشاهَد ÷ عددها</code>، والقائمة المرتّبة نسخة مرتّبة من الأفلام حسب الترتيب المختار، و«هل الترتيب حسب السنة» ليس إلا <code dir=\"ltr\">sortOrder === 'year'</code>. وتخزين أيٍّ منها يُنشئ نسخة ثانية قد تصبح قديمة.</p>\n","checkLabels":{},"optionText":{"ratings":{"text":"تقييم كل فيلم، مخزَّنًا كحقل على الفيلم داخل state الأفلام","why":"صحيح. يتغيّر التقييم حين ينقر المستخدم نجمة، ولا يمكن حسابه من أي شيء آخر."},"average-rating":{"text":"متوسّط التقييم المعروض في الترويسة","why":"مشتقّ. احسبه من تقييمات الأفلام المُشاهَدة أثناء الـ render، وإلا فسينحرف حين يتغيّر أحد التقييمات."},"sort-order":{"text":"الترتيب المختار: “title” أو “year”","why":"صحيح. يتغيّر حين يختار المستخدم من القائمة المنسدلة، ولا شيء آخر يستطيع أن يخبرك بما اختاره."},"sorted-movies":{"text":"نسخة مرتّبة من قائمة الأفلام","why":"مشتقّة. رتّب نسخة من الأفلام حسب الترتيب الحالي أثناء الـ render؛ أمّا تخزينها فيعني إعادة الترتيب يدويًا بعد كل تغيير."},"is-sorted-by-year":{"text":"قيمة منطقية <code dir=\"ltr\">isSortedByYear</code> تُستخدم لتنسيق القائمة المنسدلة","why":"مشتقّة. إنها <code dir=\"ltr\">sortOrder === 'year'</code>. وإبقاؤها في state خاص بها يسمح لها بأن تختلف عن الترتيب الفعلي."}}},"words":579,"readMinutes":3}