الحالة: ذاكرة المكوّن
غالبًا ما تحتاج المكوّنات إلى تغيير ما يظهر على الشاشة نتيجةً لتفاعل ما. يجب تحديث حقل الإدخال عند الكتابة في إستمارة ، ويجب تغيير الصورة التي يتم عرضها عند النقر فوق “التالي” في الشرائح الدوارة (image carousel) ، ويجب وضع منتج في سلة التسوق عند النقر فوق “شراء”. تحتاج المكوّنات إلى “تذكر” أشياء: قيمة الإدخال الحالية ، الصورة الحالية ، سلة التسوق. في React ، يُطلق على هذا النوع من الذاكرة المخصصة للمكوّن باسم الحالة.
You will learn
- كيفية إضافة متغير حالة باستخدام خطاف (Hook)
useState
- أي زوج من القيم يعيد خطاف
useState
- كيفية إضافة أكثر من متغير حالة واحد
- لماذا يُطلق على الحالة اسم محلية
عندما لا يكفي المتغير العادي
هنا مكوّن يقوم بتقديم صورة منحوت. يجب أن يُظهر النقر على الزر “التالي” الصورة التاليه عن طريق تغيير المؤشر index
إلى 1
، ثم 2
، وهكذا. ومع ذلك ، لن يعمل هذا (يمكنك تجربته!):
import { sculptureList } from './data.js'; export default function Gallery() { let index = 0; function handleClick() { index = index + 1; } let sculpture = sculptureList[index]; return ( <> <button onClick={handleClick}> التالي </button> <h2> <i>{sculpture.name} </i> by {sculpture.artist} </h2> <h3> ({index + 1} of {sculptureList.length}) </h3> <img src={sculpture.url} alt={sculpture.alt} /> <p> {sculpture.description} </p> </> ); }
معالج الحدث handleClick
يقوم بتحديث المتغير المحلي index
. ولكن هناك عاملين يمنعان ظهور هذا التغيير:
- المتغيرات المحلية لا تستمر بين عمليات التصيير. عندما يقوم React بتصيير هذا المكوّن للمرة الثانية، فإنه يقوم بتصييره من البداية—لا يأخذ في الاعتبار أي تغييرات في المتغيرات المحلية.
- التغييرات على المتغيرات المحلية لن تؤدي إلى تنشيط عمليات التصيير. React لا يدرك أنه يحتاج إلى إعادة تصيير المكوّن مرة أخرى بالبيانات الجديدة.
لتحديث المكوّن ببيانات جديدة، تحتاج إلى حدوث شيئين:
- الاحتفاظ بالبيانات بين عمليات التصيير.
- تنشيط React لإعادة تصيير المكوّن بالبيانات الجديدة (إعادة التصيير).
يوفر useState
هذين العنصرين:
- متغير حالة للاحتفاظ بالبيانات بين عمليات التصيير
- دالة معينة للحالة لتحديث المتغير وتنشيط React لإعادة تصيير المكوّن مرة أخرى.
إضافة متغير حالة.
لإضافة متغير حالة، استورد (import) useState
من في أعلى الملف:
import { useState } from 'react';
ثم، استبدل هذا السطر:
let index = 0;
بهذا
const [index, setIndex] = useState(0);
index
هو متغير حالة و setIndex
هو دالة التعيين.
بناء الجملة
[
و]
هنا يسمى تفكيك المصفوفات ويتيح لك قراءة القيم من مصفوفة. تحتوي المصفوفة التي يعيدهاuseState
دائمًا على عنصرين بالضبط.
هكذا يعملان معًا في handleClick
:
function handleClick() {
setIndex(index + 1);
}
الآن عند النقر على زر “التالي”، يتم تبديل النموذج النحتي الحالي:
import { useState } from 'react'; import { sculptureList } from './data.js'; export default function Gallery() { const [index, setIndex] = useState(0); function handleClick() { setIndex(index + 1); } let sculpture = sculptureList[index]; return ( <> <button onClick={handleClick}> التالي </button> <h2> <i>{sculpture.name} </i> بواسطة {sculpture.artist} </h2> <h3> ({index + 1} of {sculptureList.length}) </h3> <img src={sculpture.url} alt={sculpture.alt} /> <p> {sculpture.description} </p> </> ); }
تعرف على الخطاف الأول الخاص بك
في React, useState
, بالإضافة إلى أي وظيفة أخرى تبدأ بـ ”use
” ، يُطلق عليها اسم خطاف (Hook)
الخطافات هي وظائف خاصة تكون متاحة فقط أثناء عملية التصيير في React (سنتناول هذا بالتفصيل في الصفحة التالية). تتيح لك الخطافات “ربط” ميزات مختلفة في React.
الحالة هي احد هذه الميزات، ولكنك ستتعرف على الخطافات الأخرى لاحقًا.
تشريح useState
عند استدعاء useState
, فإنك تخبر React بأنك ترغب في أن يتذكر هذا المكوّن شيئًا:
const [index, setIndex] = useState(0);
في هذه الحالة، ترغب في أن يتذكر React القيمة الحالية ل index
.
المعامل الوحيد لuseState
هو القيمة الابتدائية لمتغير الحالة الخاص بك. في هذا المثال، تم تعيين قيمة ابتدائية لindex
ب0
باستخدام useState(0)
.
في كل مرة يتم فيها تصيير مكوّنك، يقدم useState
مصفوفة تحتوي على قيمتين:
- متغير الحالة (
index
) مع القيمة المخزنة. - دالة تحديث الحالة (
setIndex
) التي يمكنها تحديث المتغير الحالي وتقوم بتنشيط React لإعادة تصيير المكوّن مرة أخرى.
هكذا يحدث ذلك عملياً:
const [index, setIndex] = useState(0);
- يتم تصيير المكوّن الخاص بك للمرة الأولى. نظرًا لأنك قمت بتمرير القيمة
0
إلىuseState
كقيمة بدائية لـindex
, ستعيد الدالة[0, setIndex]
. يتذكر React أن القيمة0
هي أحدث قيمة للحالة. - تقوم بتحديث الحالة. عندما يقوم المستخدم بالنقر فوق الزر، يتم استدعاء
setIndex(index + 1)
. حيث أن قيمةindex
هي0
, لذالك تكونsetIndex(1)
. يخبر ذلك React بتذكر أن قيمةindex
الآن هي1
ويؤدي إلى التصيير مرة أخرى. - تصيير المكوّن الخاص بك للمرة الثانية. لا يزال React يرى
useState(0)
, ولكن بسبب أن React يتذكر أنك قمت بتعيينindex
إلى1
, فإنه يعيد[1, setIndex]
بدلاً من ذلك. - وهكذا ما إلى آخره!
إعطاء المكوّن متغيرات حالة متعددة
يمكنك أن تمتلك العديد من متغيرات الحالة من أنواع مختلفة كما تحب في مكوّن واحد. يحتوي هذا المكوّن على متغيري حالة، رقم وهو index
و منطق بوليني وهو showMore
يتم تبديله عند النقر على “إظهار التفاصيل”:
import { useState } from 'react'; import { sculptureList } from './data.js'; export default function Gallery() { const [index, setIndex] = useState(0); const [showMore, setShowMore] = useState(false); function handleNextClick() { setIndex(index + 1); } function handleMoreClick() { setShowMore(!showMore); } let sculpture = sculptureList[index]; return ( <> <button onClick={handleNextClick}> التالي </button> <h2> <i>{sculpture.name} </i> بواسطة {sculpture.artist} </h2> <h3> ({index + 1} of {sculptureList.length}) </h3> <button onClick={handleMoreClick}> {showMore ? 'إخفاء' : 'إظهار'} التفاصيل </button> {showMore && <p>{sculpture.description}</p>} <img src={sculpture.url} alt={sculpture.alt} /> </> ); }
من الجيد أن يكون لديك متغيرات حالة متعددة إذا كانت حالتها غير متصلة ، مثل index
و showMore
في المثال السابق. لكن إذا وجدت أنك غالبًا ما تقوم بتغيير متغيري حالة اثنين معًا، فقد يكون من الأسهل دمجهما في متغير واحد. على سبيل المثال ، إذا كان لديك استمارة تحتوي على العديد من الحقول ، فمن الأكثر ملاءمة أن يكون لديك متغير حالة واحد يحتوي على كائن بدلاً من متغير حالة لكل حقل. اقرأ ختيار هيكل الحالة للحصول على المزيد من النصائح.
Deep Dive
قد تكون لاحظت أن استدعاء useState
لا يتلقى أي معلومات حول متغير الحالة الذي يشير إليه. لا يوجد “معرف” يتم تمريره إلى useState
، فكيف يعرف أي من المتغيرات الحالة يجب إرجاعه؟ هل يعتمد على بعض السحر مثل تحليل الوظائفك؟ الإجابة هي لا.
بدلاً من ذلك ، الخطافات تعتمد على ترتيب استدعاء ثابت في كل مرة يتم فيها تصيير نفس المكوّن. هذا يعمل بشكل جيد في الواقع لأنه إذا اتبعت القاعدة أعلاه (“استدعاء الخطافات فقط في الطبقة العليا”) ، فسيتم استدعاء الخطافات دائمًا بنفس الترتيب. بالإضافة إلى ذلك ، يمتلك ملحق تحليل يلتقط معظم الأخطاء.
داخليًا ، يحتفظ React بمصفوفة من أزواج الحالة لكل مكوّن. كما يحتفظ بفهرس الزوج الحالي ، والذي يتم تعيينه على 0
قبل التصيير. في كل مرة تستدعي فيها useState
، يعطيك React الزوج التالي من الحالة ويزيد من الفهرس. يمكنك قراءة المزيد عن هذه الآلية في خطافات React: ليست سحراً، فقط مصفوفات
هذا المثال لا يستخدم React ولكنه يعطيك فكرة عن كيفية عمل useState
داخليًا:
let componentHooks = []; let currentHookIndex = 0; // كيف تعمل useState داخل React (مبسطة). function useState(initialState) { let pair = componentHooks[currentHookIndex]; if (pair) { // هذا ليس التصيير الاول، // لذالك زوج الحالة موجود مسبقاً . // ارجعه و جهز لإستدعاء الخطاف القادم. currentHookIndex++; return pair; } // هذه هي المرة الاولى التي نقوم فيها بالتصيير، // لذالك اصنع زوج الحالة وقم بتخزينه. pair = [initialState, setState]; function setState(nextState) { // عندما يطلب المستخدم تغيير حالة، // ضع القيمة الجدبدة الي الزوج pair[0] = nextState; updateDOM(); } // قم بتخزين الزوج للتصييرات القادمه // و جهز لإستدعاء الخطاف القادم. componentHooks[currentHookIndex] = pair; currentHookIndex++; return pair; } function Gallery() { // كل إستدعاء useState() سيحصل على الزوج القادم. const [index, setIndex] = useState(0); const [showMore, setShowMore] = useState(false); function handleNextClick() { setIndex(index + 1); } function handleMoreClick() { setShowMore(!showMore); } let sculpture = sculptureList[index]; // هذا المثال لا يستخدم ريأكت، لذا // قم بإرجاع كائن إخراج بدلاً من JSX return { onNextClick: handleNextClick, onMoreClick: handleMoreClick, header: `${sculpture.name} بواسطة ${sculpture.artist}`, counter: `${index + 1} من ${sculptureList.length}`, more: `${showMore ? 'إخفاء' : 'إظهار'} التفاصيل`, description: showMore ? sculpture.description : null, imageSrc: sculpture.url, imageAlt: sculpture.alt }; } function updateDOM() { // أعد تعيين فهرس الخطاف الحالي // قبل تصيير المكوّن. currentHookIndex = 0; let output = Gallery(); // حدث ال DOM لتُطابق الإخراج. // هذا هو الجزء الذي يقوم به React من أجلك nextButton.onclick = output.onNextClick; header.textContent = output.header; moreButton.onclick = output.onMoreClick; moreButton.textContent = output.more; image.src = output.imageSrc; image.alt = output.imageAlt; if (output.description !== null) { description.textContent = output.description; description.style.display = ''; } else { description.style.display = 'none'; } } let nextButton = document.getElementById('nextButton'); let header = document.getElementById('header'); let moreButton = document.getElementById('moreButton'); let description = document.getElementById('description'); let image = document.getElementById('image'); let sculptureList = [{ name: 'تحية لجراحة الأعصاب', artist: 'مارتا كولفين أندرادي', description: 'على الرغم من أن كولفين معروفة بشكل أساسي بالمواضيع المجردة التي تلمح إلى الرموز ما قبل الهيسبانية، إلا أن هذا التمثال العملاق، تحية لجراحة الأعصاب، هو واحد من أكثر قطع الفن العامة التي يمكن التعرف عليها.', url: 'https://i.imgur.com/Mx7dA2Y.jpg', alt: 'تمثال من البرونز ليدين متقاطعتين تحملان براغين الدماغ البشري بأطراف أصابعهما بعناية.' }, { name: 'فلوراليس جينيريكا', artist: 'إدواردو كاتالانو', description: 'هذه الزهرة الفضية الضخمة (75 قدمًا أو 23 مترًا) تقع في بوينس آيرس. تم تصميمها للتحرك، حيث تُغلق بتلاتها في المساء أو عندما تكون الرياح قوية وتُفتح في الصباح.', url: 'https://i.imgur.com/ZF6s192m.jpg', alt: 'تمثال ضخم من المعدن الفضي يتميز بتلات مرآة تعكس الضوء وسيقان قوية.' }, { name: 'الوجود الأبدي', artist: 'جون وودرو ويلسون', description: 'شتُهر ويلسون بشدقه بالمساواة والعدالة الاجتماعية، وكذلك الصفات الأساسية والروحية للبشرية. يمثل هذا التمثال البرونزي الضخم (7 أقدام أو 2.13 متر) ما وصفه بأنه "وجود أسود رمزي مشبوب بشعور بالإنسانية العالمية"', url: 'https://i.imgur.com/aTtVpES.jpg', alt: 'التمثال الذي يصوّر رأس إنسان يبدو حاضرًا وجديًا دائمًا. إنه ينبعث منه الهدوء والسكينة.' }, { name: 'مواي', artist: 'فنان مجهول', description: 'تقع على جزيرة الفصح، وهناك 1000 تمثال مواي، أو تماثيل ضخمة موجودة، تم إنشاؤها من قبل شعب رابا نوي الأول في وقت مبكر، ويعتقد البعض أنها تمثل أسلافًا مجسدين.', url: 'https://i.imgur.com/RCwLEoQm.jpg', alt: 'ثلاثة تماثيل حجرية ضخمة لرؤوس بأوجه كبيرة نسبيًا وتعابير وجوه متجهمة.' }, { name: 'نانا الزرقاء', artist: 'نيكي دي سانت فال', description: 'النانا هي مخلوقات ظافرة، رموز للأنوثة والأمومة. في البداية، استخدمت سانت فال القماش والأشياء المعثور عليها للنانا، وفي وقت لاحق قدمت البوليستر لتحقيق تأثير أكثر حيوية.', url: 'https://i.imgur.com/Sd1AgUOm.jpg', alt: 'تمثال موزاييكي كبير لشخصية أنثوية راقصة غريبة في زي ملون تنبع منها الفرح.' }, { name: 'النموذج النهائي', artist: 'باربارا هيبورث', description: 'هذا التمثال البرونزي المجرد هو جزء من سلسلة "عائلة الإنسان" الموجودة في حديقة يوركشاير للنحت. اختارت هيبورث عدم إنشاء تمثيلات حرفية للعالم ولكنها طوّرت أشكالًا مجردة مستوحاة من البشر والمناظر الطبيعية..', url: 'https://i.imgur.com/2heNQDcm.jpg', alt: 'تمثال طويل مصنوع من ثلاثة عناصر مرصوصة فوق بعضها البعض تشبه شكل إنسان.' }, { name: 'كافاليير', artist: 'لاميدي أولونادي فاكيهي', description: "نزلت أعمال فاكيهي من أربعة أجيال من نحاتي الخشب، ودمجت أعماله بين المواضيع التقليدية واليوروبية المعاصرة.", url: 'https://i.imgur.com/wIdGuZwm.png', alt: 'تمثال خشبي معقد لمحارب ذو وجه مركزي على حصان مزين بزخارف.' }, { name: 'بطون كبيرة', artist: 'ألينا شابوتشنيكوف', description: "شابوتشنيكوف معروفة بتماثيلها المكسورة للجسم كاستعارة لهشاشة وعدم الدوام للشباب والجمال. يصور هذا التمثال بطونًا كبيرة واقعية جدًا مكدسة فوق بعضها البعض، تبلغ ارتفاع كل واحدة حوالي خمسة أقدام (1.5 متر).", url: 'https://i.imgur.com/AlHTAdDm.jpg', alt: 'التمثال يذكر بشلال من الطيات، مختلف تمامًا عن البطون في التماثيل الكلاسيكية.' }, { name: 'Terracotta Army', artist: 'فنان غير معروف', description: 'جيش التراكوتا هو مجموعة من تماثيل التراكوتا تصور جيوش قين شي هوانغ، أول إمبراطور للصين. يتألف الجيش من أكثر من 8000 جندي، و130 عربة مع 520 حصانًا، و150 حصانًا فرسانيًا.', url: 'https://i.imgur.com/HMFmH6m.jpg', alt: '12 تمثالًا من التراكوتا لمحاربين جادين، يتميز كل منهم بتعبير وجه فريد' }, { name: 'منظر طبيعي قمري', artist: 'لويز نيفلسون', description: 'كانت نيفلسون معروفة بالتجميع من الأشياء المتناثرة في شوارع مدينة نيويورك، والتي كانت تقوم بتجميعها لاحقًا في إبداعات ضخمة. في هذا العمل، استخدمت أجزاء متنوعة مثل ساق سرير، وعصا تلاعب، وجزء من مقعد، وقامت بتثبيتها ولصقها في صناديق تعكس تأثير التجريد الهندسي للمكعبات في الفن التكعيبي على الفضاء والشكل.', url: 'https://i.imgur.com/rN7hY6om.jpg', alt: ' منحوتة سوداء مطفأة حيث يكون من الصعب في البداية تمييز العناصر الفردية.' }, { name: 'هالة ضوئية', artist: ' رانجاني شيتار', description: 'تدمج شيتار بين التقاليد والحداثة، وبين الطبيعة والصناعة. يركز فنها على العلاقة بين الإنسان والطبيعة. وقد وصفت أعمالها بأنها جذابة بشكل مجرد ومجازي، وتتحدى الجاذبية، وتمثل "توليفًا رائعًا لمواد غير متوقعة."', url: 'https://i.imgur.com/okTpbHhm.jpg', alt: 'منحوتة شبيهة بالأسلاك الفاتحة مركبة على جدار من الخرسانة وممتدة على الأرض. تبدو خفيفة.' }, { name: 'فرسان نهر', artist: 'حديقة حيوان تايبيه', description: 'قد قامت حديقة حيوان تايبيه بطلب ساحة للفرسان النهريين تتضمن فرسان النهر المغمورين في اللعب.', url: 'https://i.imgur.com/6o5Vuyu.jpg', alt: 'مجموعة من منحوتات فرسان النهر المصنوعة من البرونز تظهر وكأنها تسبح خارجة من الرصيف كأنها تسبح.' }]; // إجعل واجهة المستخدم تطابق الحالة الاولى updateDOM();
لا تحتاج إلى فهمه لاستخدام React ، ولكن قد تجد هذا النموذج العقلي مفيدًا.
الحالة معزولة وخاصة
الحالة محلية لمثيل المكوّن على الشاشة. بعبارة أخرى ، إذا قمت بتصيير نفس المكوّن مرتين ، فستملك كل نسخة حالة معزولة تمامًا! تغيير واحد منها لن يؤثر على الآخر.
في هذا المثال ، يتم تصيير مكوّن Gallery
من وقت سابق مرتين دون أي تغيير في منطقه. حاول النقر على الأزرار داخل كل من المعارض. لاحظ أن حالتها مستقلة.
import Gallery from './Gallery.js'; export default function Page() { return ( <div className="Page"> <Gallery /> <Gallery /> </div> ); }
هذا هو ما يجعل الحالة مختلفة عن المتغيرات العادية التي قد تعلنها في أعلى الوحدة الخاصة بك. الحالة ليست مرتبطة بإستدعاء وظيفة معينة أو مكان في الكود ، ولكنها “محلية” للمكان المحدد على الشاشة. قمت بتصيير اثنين من مكوّنات <Gallery />
لذا يتم تخزين حالتها بشكل منفصل.
لاحظ أيضًا كيف أن مكوّن ال Page
لا “يعرف” أي شيء عن حالة Gallery
أو حتى ما إذا كان لديها أي حالة. على عكس الخصائص (props) الحالة خاصة تمامًا للمكوّن الذي يعلنها. لا يمكن للمكوّن الأب تغييرها. يتيح لك ذلك إضافة حالة إلى أي مكوّن أو إزالتها دون التأثير على بقية المكوّنات.
ماذا لو أردت أن تحافظ على حالة كلتا المعرضين متزامنتين؟ الطريقة الصحيحة للقيام بذلك في React هي إزالة الحالة من المكوّنات الفرعية وإضافتها إلى أقرب والديها المشترك. ستركز الصفحات القليلة المقبلة على تنظيم حالة مكوّن واحد فقط، ولكننا سنعود إلى هذا الموضوع في مشاركة الحالة بين المكوّنات.
Recap
- استخدم متغير الحالة عندما يحتاج المكوّن إلى “تذكر” بعض المعلومات بين عمليات التصيير.
- يتم إعلان متغيرات الحالة عن طريق استدعاء خطاف
useState
. - الخطافات هي وظائف خاصة تبدأ بـ
use
. تتيح لك “ربط” ميزات React مثل الحالة. - قد تذكرك الخطافات بالاستيرادات: يجب استدعاؤها بغض النظر عن الشروط. إستدعاء الخطفات ، بما في ذلك
useState
, صالح فقط على المستوى الأعلى للمكوّن أو خطاف آخر . - يعيد خطاف
useState
زوجًا من القيم: الحالة الحالية والدالة لتحديثها. - يمكنك أن تمتلك أكثر من متغير حالة واحد. داخليًا ، يقوم React بمطابقتها حسب ترتيبها.
- الحالة خاصة بالمكوّن. إذا قمت بتصييره في مكانين ، تحصل كل نسخة على حالتها الخاصة.
Challenge 1 of 4: أكمل المعرض
عند الضغط على “التالي” في آخر منحوتة ، يتعطل الكود. قم بتصحيح المنطق لمنع التعطل. يمكنك القيام بذلك عن طريق إضافة منطق إضافي إلى معالج الحدث أو عن طريق تعطيل الزر عندما لا يكون الإجراء ممكنًا.
بعد إصلاح التعطل ، أضف زر “السابق” الذي يعرض المنحوتة السابقة. لا يجب أن يتعطل عند الوصول إلى أول منحوتة.
import { useState } from 'react'; import { sculptureList } from './data.js'; export default function Gallery() { const [index, setIndex] = useState(0); const [showMore, setShowMore] = useState(false); function handleNextClick() { setIndex(index + 1); } function handleMoreClick() { setShowMore(!showMore); } let sculpture = sculptureList[index]; return ( <> <button onClick={handleNextClick}> التالي </button> <h2> <i>{sculpture.name} </i> بواسطة {sculpture.artist} </h2> <h3> ({index + 1} of {sculptureList.length}) </h3> <button onClick={handleMoreClick}> {showMore ? 'إخفاء' : 'إظهار'} التفاصيل </button> {showMore && <p>{sculpture.description}</p>} <img src={sculpture.url} alt={sculpture.alt} /> </> ); }