رفع الحالة يعني نقل الحالة من المكوّنات التي تحتاج إلى مشاركتها إلى أقرب أب مشترك لها. يحتفظ الأب بالقيمة، ويمررها إلى الأسفل كـ prop، ويمرر دالة يستدعيها الأبناء لتغييرها، فيُعرض كل ابن من نسخة واحدة من الحالة.
انقر "Show" على أي لوحة: تنفتح وتنغلق اللوحة التي كانت مفتوحة. لا يمكن أن تنفتح إلا لوحة واحدة في كل مرة لأن اللوحات لا تقرر ذلك بنفسها. يحتفظ App بالقيمة openIndex، ولا يستقبل كل Panel إلا isOpen ودالة onOpen.
المشكلة: حالة ينبغي أن تتفق
ابدأ من النسخة البديهية، حيث تملك كل لوحة حالتها isOpen. كل لوحة تعمل، لكن اللوحات لا تعرف شيئًا عن بعضها، فلا شيء يمنع اثنتين منها من الانفتاح معًا.
افتح اللوحتين: تبقيان مفتوحتين معًا. الحالة في React خاصة بالمكوّن الذي يعلنها، فلا يستطيع الشقيق قراءتها أو إعادة ضبطها. عندما يحتاج مكوّنان إلى الاتفاق، يجب أن تعيش الحالة فوقهما كليهما.
رفع الحالة في ثلاث خطوات
تحويل المثال الثاني إلى الأول يتطلب ثلاثة تعديلات.
- أزل الحالة من الابن. احذف
useStateفيPanelواقرأisOpenمن props بدلًا منها. لم يعد الابن يقرر ما إذا كان مفتوحًا. - مرّر القيمة ووسيلة لتغييرها من الأب. يحصل
PanelعلىisOpenودالة استدعاءonOpen. يستدعي الابنonOpen()عندما يُنقر زره؛ ولا يعرف ما يفعله الأب بذلك. - أضف الحالة إلى الأب المشترك. يعلن
AppالقيمةopenIndexويحوّلها إلى props لكل لوحة:isOpen={openIndex === 1}وonOpen={() => setOpenIndex(1)}.
أقرب أب مشترك هو أدنى مكوّن يعرض كل المكوّنات التي تحتاج إلى الحالة. وهو هنا App. لو كانت اللوحات داخل مكوّن Faq، لذهبت الحالة إلى Faq، لا أعلى منه.
بعد الرفع، يكون Panel متحكمًا به من أبيه بالمعنى نفسه الذي للحقل المتحكم به: يعرض ما تقوله props ويبلّغ عن التغييرات عبر دالة استدعاء. تتناول صفحة المكوّنات المتحكم بها وغير المتحكم بها الفكرة نفسها لعناصر النماذج.
مصدر وحيد للحقيقة
عندما يعرض جزءان من الشاشة الحقيقة نفسها، خزّن تلك الحقيقة مرة واحدة واحسب كل ما عداها منها. محوّل درجات الحرارة هو الحالة الكلاسيكية: يجب أن يتفق حقلا Celsius وFahrenheit دائمًا، فلا يمكن أن يحتفظ كل منهما برقمه الخاص.
اكتب في أي من الحقلين فيتبعه الآخر. الحالة حقيقة واحدة: الرقم الذي كتبه المستخدم آخر مرة والمقياس الذي كان فيه. ويُحسب الحقل الآخر منها أثناء العرض، فيحتفظ الحقل الذي تكتب فيه دائمًا بما كتبته بالضبط. غيّر الحالة الابتدائية إلى { value: '212', scale: 'f' } فتتحول الرسالة أسفل الحقلين إلى "Water boils."
الاحتفاظ برقم Celsius ورقم Fahrenheit في قطعتين من الحالة يعني أن على كل معالج تحديث الاثنين، وفي أول مرة ينسى فيها معالج ذلك يختلف الحقلان. أما قيمة واحدة مخزنة فلا يمكن أن تختلف مع نفسها.
تمرير دالة الضبط إلى الأسفل
لا يستطيع الابن تغيير حالة الأب إلا عبر دالة يعطيه إياها الأب. يمكنك تمرير دالة الضبط نفسها (onSelect={setColor}) أو دالة تفعل أكثر (onOpen={() => setOpenIndex(1)}). إعطاء الـ prop اسمًا بأسلوب الأحداث مثل onSelect أو onChange، بدلًا من setSelected، يُبقي الابن غير مدرك لكيفية تخزين الأب للقيمة، فيستطيع الأب تغيير ذلك لاحقًا دون لمس الابن.
لا يتحدث ColorPicker وPreview مع بعضهما أبدًا. يبلّغ منتقي اللون عن اختيار إلى الأعلى، ويخزنه App، وتتدفق القيمة الجديدة إلى الأسفل إلى كليهما.
متى لا ترفع الحالة
للرفع تكلفة. عندما تعيش الحالة في أب، يعيد كل تغيير عرض الأب، وافتراضيًا كل أبنائه، بما فيهم من لا يستخدمون الحالة. ارفع فقط حتى أقرب أب مشترك، واترك الحالة التي لا يستخدمها إلا مكوّن واحد داخل ذلك المكوّن.
اكتب في المربع وراقب وحدة التحكم: لا يُعرض مع كل ضغطة مفتاح إلا SearchBox. انقل الآن query إلى App ومررها إلى SearchBox كـ props. عندها تسجّل كل ضغطة مفتاح ProductList أيضًا، رغم أن القائمة لا تستخدم الاستعلام. لو كانت القائمة ترشح بحسب الاستعلام، لكان الرفع هو القرار الصحيح، لأن المكوّنين سيعتمدان حينها على القيمة نفسها.
السؤال الذي تطرحه هو «من يحتاج إلى قراءة هذه القيمة؟» إذا كان الجواب مكوّنًا واحدًا، تبقى الحالة هناك. وإذا كان عدة مكوّنات، تذهب إلى أقرب أب مشترك لها.
عندما يذهب الرفع بعيدًا جدًا
أحيانًا يكون أقرب أب مشترك بعيدًا في أعلى الشجرة، ويجب تمرير القيمة عبر عدة مكوّنات لا تفعل سوى تسليمها. هذا هو تمرير props عبر الطبقات. بضع طبقات من props لا بأس بها وسهلة التتبع. وعندما تسافر القيمة نفسها عبر طبقات كثيرة، أو يحتاجها كل مكوّن تقريبًا (المستخدم المسجّل، أو السمة، أو اللغة)، فاقرأها بـ useContext بدلًا من تمريرها يدويًا. يغيّر السياق كيفية وصول القيمة إلى الأبناء؛ أما الحالة نفسها فما زالت تعيش في أب واحد، فهي ما زالت مرفوعة.
الأسئلة الشائعة
ماذا يعني رفع الحالة في React؟
نقل قطعة من الحالة من المكوّنات التي تستخدمها إلى أقرب أب مشترك لها. يملك الأب الحالة ويمرر القيمة، مع دالة لتغييرها، إلى الأبناء كـ props.
كيف يتشارك مكوّنان شقيقان الحالة في React؟
لا يستطيع الأشقاء قراءة حالة بعضهم. ضع الحالة في الأب المشترك، ومرر القيمة إلى كليهما، ومرر دالة ضبط (أو معالجًا مثل onChange) إلى الذي يغيّرها. عندها يُعرض الشقيقان كلاهما من القيمة نفسها.
كيف يحدّث المكوّن الابن حالة الأب؟
يمرر الأب دالة كـ prop، مثل onSelect={setSelected} أو onSelect={(id) => setSelected(id)}، ويستدعيها الابن. تبقى الحالة في الأب؛ والابن يطلب التغيير فقط.
متى لا ينبغي رفع الحالة؟
عندما لا يستخدم الحالة إلا مكوّن واحد. رفعها أعلى من اللازم يجعل الأب يُعاد عرضه مع كل تغيير وينشر props عبر مكوّنات لا تهتم بها. أبقِ الحالة أقرب ما يمكن إلى حيث تُستخدم.
ما البديل عن رفع الحالة أكثر من اللازم؟
إذا وجدت نفسك تمرر props نفسها عبر طبقات كثيرة، فاقرأ القيمة المشتركة بالسياق (useContext) بدلًا من ذلك، أو أعد الهيكلة لتكون المكوّنات التي تحتاجها أقرب بعضها إلى بعض.