v19.2Latest

اختيار بنية الحالة

يمكن لهيكلة الحالة بشكل جيد أن تُحدث فرقًا بين مكون سهل التعديل والتشخيص، وآخر يكون مصدرًا دائمًا للأخطاء. فيما يلي بعض النصائح التي يجب أن تأخذها في الاعتبار عند هيكلة الحالة.

سوف تتعلم
  • متى تستخدم متغير حالة واحد مقابل متغيرات حالة متعددة
  • ما يجب تجنبه عند تنظيم الحالة
  • كيفية إصلاح المشكلات الشائعة في بنية الحالة

مبادئ هيكلة الحالة

عندما تكتب مكونًا يحتفظ ببعض الحالة، سيتعين عليك اتخاذ خيارات حول عدد متغيرات الحالة التي ستستخدمها وما يجب أن يكون شكل بياناتها. بينما من الممكن كتابة برامج صحيحة حتى مع بنية حالة غير مثالية، هناك بعض المبادئ التي يمكن أن توجهك لاتخاذ خيارات أفضل:

  1. اجمع الحالة ذات الصلة.إذا كنت تقوم دائمًا بتحديث متغيرين أو أكثر من متغيرات الحالة في نفس الوقت، ففكر في دمجهما في متغير حالة واحد.
  2. تجنب التناقضات في الحالة.عندما تكون الحالة مهيكلة بطريقة قد تتعارض فيها عدة أجزاء من الحالة وتختلف مع بعضها البعض، فإنك تترك مجالًا للأخطاء. حاول تجنب ذلك.
  3. تجنب الحالة الزائدة عن الحاجة.إذا كان بإمكانك حساب بعض المعلومات من خاصيات المكون أو متغيرات حالته الموجودة أثناء التصيير، فلا يجب عليك وضع تلك المعلومات في حالة ذلك المكون.
  4. تجنب التكرار في الحالة.عندما يتم تكرار نفس البيانات بين متغيرات حالة متعددة، أو داخل كائنات متداخلة، يصبح من الصعب الحفاظ على تزامنها. قلل التكرار عندما تستطيع.
  5. تجنب الحالة المتداخلة بعمق.الحالة ذات التسلسل الهرمي العميق ليست مريحة جدًا للتحديث. عندما يكون ذلك ممكنًا، يُفضل هيكلة الحالة بطريقة مسطحة.

الهدف من وراء هذه المبادئ هوجعل الحالة سهلة التحديث دون إدخال أخطاء. إزالة البيانات الزائدة والمكررة من الحالة يساعد في ضمان بقاء جميع أجزائها متزامنة. هذا يشبه كيف قد يرغب مهندس قاعدة البيانات في"تطبيع" بنية قاعدة البياناتلتقليل فرص حدوث الأخطاء. نقلاً عن ألبرت أينشتاين،"اجعل حالتك بسيطة قدر الإمكان - ولكن ليس أبسط من ذلك."

الآن دعنا نرى كيف تنطبق هذه المبادئ عمليًا.

قد تكون غير متأكد أحيانًا بين استخدام متغير حالة واحد أو متغيرات حالة متعددة.

هل يجب أن تفعل هذا؟

أم هذا؟

من الناحية الفنية، يمكنك استخدام أي من هذين الأسلوبين. لكنإذا كان متغيرا حالة معينين يتغيران دائمًا معًا، فقد يكون من الجيد توحيدهما في متغير حالة واحد.عندها لن تنسى دائمًا الحفاظ على تزامنهما، كما في هذا المثال حيث يؤدي تحريك المؤشر إلى تحديث إحداثيات النقطة الحمراء معًا:

حالة أخرى ستجمع فيها البيانات في كائن أو مصفوفة هي عندما لا تعرف عدد أجزاء الحالة التي ستحتاجها. على سبيل المثال، يكون ذلك مفيدًا عندما يكون لديك نموذج يمكن للمستخدم إضافة حقول مخصصة إليه.

مأزق

إذا كان متغير حالتك كائنًا، تذكر أنلا يمكنك تحديث حقل واحد فقط فيهدون نسخ الحقول الأخرى صراحةً. على سبيل المثال، لا يمكنك فعلsetPosition({ x: 100 })في المثال أعلاه لأنه لن يحتوي على خاصيةyعلى الإطلاق! بدلاً من ذلك، إذا أردت تعيينxبمفرده، يمكنك إما فعلsetPosition({ ...position, x: 100 })، أو تقسيمهما إلى متغيري حالة وفعلsetX(100).

تجنب التناقضات في الحالة

إليك نموذج تعليقات فندق يحتوي على متغيري حالةisSending و isSent:

بينما يعمل هذا الكود، فإنه يترك الباب مفتوحًا أمام حالات "مستحيلة". على سبيل المثال، إذا نسيت استدعاءsetIsSent و setIsSendingمعًا، قد ينتهي بك الأمر في حالة يكون فيها كل منisSending و isSentبقيمةtrueفي نفس الوقت. كلما كان المكون أكثر تعقيدًا، كلما كان من الصعب فهم ما حدث.

بما أنisSending و isSentيجب ألا يكونا بقيمةtrueفي نفس الوقت، فمن الأفضل استبدالهما بمتغير حالة واحدstatusيمكن أن يأخذ إحدىثلاثحالات صالحة:'typing'(البدائية)،'sending'، و'sent':

لا يزال بإمكانك تعريف بعض الثوابت لتحسين قابلية القراءة:

لكنها ليست متغيرات حالة، لذا لا داعي للقلق بشأن عدم تزامنها مع بعضها البعض.

تجنب الحالة الزائدة

إذا كان بإمكانك حساب بعض المعلومات من خاصيات المكون أو متغيرات حالته الحالية أثناء التصيير،يجب ألاتضع تلك المعلومات في حالة ذلك المكون.

على سبيل المثال، خذ هذا النموذج. إنه يعمل، لكن هل يمكنك العثور على أي حالة زائدة فيه؟

يحتوي هذا النموذج على ثلاثة متغيرات حالة:firstName،lastName، وfullName. ومع ذلك، فإنfullNameزائد.يمكنك دائمًا حسابfullNameمنfirstName و lastNameأثناء التصيير، لذا قم بإزالته من الحالة.

إليك كيف يمكنك فعل ذلك:

هنا،fullName ليسمتغير حالة. بدلاً من ذلك، يتم حسابه أثناء التصيير:

نتيجة لذلك، لا تحتاج معالجات التغيير إلى فعل أي شيء خاص لتحديثه. عندما تستدعيsetFirstNameأوsetLastName، فإنك تُطلق إعادة تصيير، وبعدها سيتم حسابfullNameالتالي من البيانات الجديدة.

Deep Dive
لا تعكس الخصائص في الحالة

تجنب التكرار في الحالة

يتيح لك مكون قائمة القائمة هذا اختيار وجبة خفيفة سفر واحدة من بين عدة خيارات:

حاليًا، يقوم بتخزين العنصر المحدد ككائن في متغير الحالةselectedItem. ومع ذلك، هذا ليس جيدًا:محتوى selectedItemهو نفس الكائن الموجود داخل قائمةitems.وهذا يعني أن معلومات العنصر نفسه مكررة في مكانين.

لماذا هذه مشكلة؟ دعنا نجعل كل عنصر قابلًا للتعديل:

لاحظ كيف إذا قمت بالنقر أولاً على "Choose" لعنصر ماثمقمت بتعديله،فإن حقل الإدخال يتم تحديثه ولكن التسمية في الأسفل لا تعكس التعديلات.هذا لأنك كررت الحالة، ونسيت تحديثselectedItem.

على الرغم من أنه يمكنك تحديثselectedItemأيضًا، إلا أن الإصلاح الأسهل هو إزالة التكرار. في هذا المثال، بدلاً من كائنselectedItem(الذي يخلق تكرارًا مع الكائنات داخلitems)، يمكنك الاحتفاظ بـselectedIdفي الحالة،ثمالحصول علىselectedItemعن طريق البحث في مصفوفةitemsعن عنصر بهذا المعرف:

كانت الحالة مكررة على النحو التالي:

  • items = [{ id: 0, title: 'pretzels'}, ...]
  • selectedItem = {id: 0, title: 'pretzels'}

ولكن بعد التغيير أصبحت على النحو التالي:

  • items = [{ id: 0, title: 'pretzels'}, ...]
  • selectedId = 0

لقد اختفى التكرار، وأنت تحتفظ فقط بالحالة الأساسية!

الآن إذا قمت بتحرير العنصرالمحدد، فسيتم تحديث الرسالة أدناه على الفور. هذا لأنsetItemsيؤدي إلى إعادة التصيير، وitems.find(...)سيجد العنصر ذو العنوان المحدث. لم تكن بحاجة إلى الاحتفاظبالعنصر المحددفي الحالة، لأن فقطمعرف العنصر المحددهو الأساسي. يمكن حساب الباقي أثناء التصيير.

تجنب الحالة المتداخلة بعمق

تخيل خطة سفر تتكون من كواكب وقارات ودول. قد تميل إلى تنظيم حالتها باستخدام كائنات ومصفوفات متداخلة، كما في هذا المثال:

لنفترض الآن أنك تريد إضافة زر لحذف مكان قمت بزيارته بالفعل. كيف ستقوم بذلك؟تحديث الحالة المتداخلةيتضمن إنشاء نسخ من الكائنات على طول المسار من الجزء الذي تغير. حذف مكان متداخل بعمق سيتضمن نسخ سلسلة الأماكن الأصلية بأكملها. مثل هذا الرمز يمكن أن يكون مطولاً جداً.

إذا كانت الحالة متداخلة بعمق بحيث يصعب تحديثها، ففكر في جعلها "مسطحة".إليك طريقة واحدة يمكنك من خلالها إعادة هيكلة هذه البيانات. بدلاً من بنية شبيهة بالشجرة حيث يحتوي كلمكانعلى مصفوفة منأماكنه الفرعية، يمكن أن يحتفظ كل مكان بمصفوفة منمعرفات أماكنه الفرعية. ثم قم بتخزين تعيين من كل معرف مكان إلى المكان المقابل.

قد تذكرك إعادة هيكلة البيانات هذه برؤية جدول قاعدة بيانات:

الآن بعد أن أصبحت الحالة "مسطحة" (تُعرف أيضًا باسم "معيارية")، أصبح تحديث العناصر المتداخلة أسهل.

لإزالة مكان الآن، تحتاج فقط إلى تحديث مستويين من الحالة:

  • يجب أن تستبعد النسخة المحدثة من مكانهالأبالمعرف المُزال من مصفوفةchildIdsالخاصة به.
  • يجب أن تتضمن النسخة المحدثة من كائن الجدول الجذري "root" النسخة المحدثة من المكان الأب.

إليك مثالاً على كيفية القيام بذلك:

يمكنك تداخل الحالة بقدر ما تريد، لكن جعلها "مسطحة" يمكن أن يحل العديد من المشاكل. فهي تجعل تحديث الحالة أسهل، وتساعد على ضمان عدم وجود تكرار في أجزاء مختلفة من كائن متداخل.

في بعض الأحيان، يمكنك أيضًا تقليل تداخل الحالة عن طريق نقل بعض الحالة المتداخلة إلى المكونات الفرعية. هذا يعمل بشكل جيد مع حالة واجهة المستخدم العابرة التي لا تحتاج إلى تخزين، مثل ما إذا كان العنصر محل تمرير مؤشر الفأرة.

ملخص

  • إذا كان متغيرا حالة يتم تحديثهما معًا دائمًا، ففكر في دمجهما في متغير واحد.
  • اختر متغيرات الحالة الخاصة بك بعناية لتجنب إنشاء حالات "مستحيلة".
  • هيكل حالتك بطريقة تقلل من احتمالية ارتكاب خطأ أثناء تحديثها.
  • تجنب الحالة الزائدة والمكررة حتى لا تحتاج إلى الحفاظ على تزامنها.
  • لا تضع الخصائصفيالحالة إلا إذا كنت تريد منع التحديثات بشكل محدد.
  • لأنماط واجهة المستخدم مثل التحديد، احتفظ بالمعرف أو الفهرس في الحالة بدلاً من الكائن نفسه.
  • إذا كان تحديث الحالة المتداخلة بعمق معقدًا، حاول تسطيحها.

Try out some challenges

Challenge 1 of 4:Fix a component that’s not updating #

This Clock component receives two props: color and time. When you select a different color in the select box, the Clock component receives a different color prop from its parent component. However, for some reason, the displayed color doesn’t update. Why? Fix the problem.