useState هو الخطاف الذي يتيح للمكوّن أن يتذكر شيئًا بين مرات العرض: عددًا، أو النص في حقل إدخال، أو ما إذا كانت قائمة مفتوحة. في كل مرة تستدعي دالة الضبط الخاصة به، تعرض React المكوّن من جديد بالقيمة الجديدة.
انقر الزر، ثم غيّر useState(0) إلى useState(10) في المحرر. تُعاد بناء المعاينة ويبدأ العدد من 10.
الصيغة
const [value, setValue] = useState(initialValue);
- تُستخدم
initialValueفي العرض الأول فقط. وفي كل عرض لاحق تتجاهلها React وتعيد القيمة التي خزّنتها. valueهي القيمة الحالية لهذا العرض.- تستبدل
setValueالقيمة وتجدول عرضًا جديدًا.
الأسماء من اختيارك. يُرجع useState مصفوفة من عنصرين والأقواس المربعة تفككها، فيكون [isOpen, setIsOpen] و[email, setEmail] النمط نفسه.
استدعِ useState في المستوى الأعلى من مكوّنك، ولا تستدعه أبدًا داخل حلقة أو شرط أو دالة متداخلة. تطابق React كل استدعاء مع قيمته المخزنة بحسب ترتيب الاستدعاءات، لذا يجب أن يكون هذا الترتيب نفسه في كل عرض (راجع قواعد الخطافات).
عدة قطع من الحالة
يمكن للمكوّن أن يستدعي useState بقدر ما يحتاج. كل استدعاء قيمة مستقلة بذاتها.
الحالة لقطة
دالة الضبط لا تغيّر المتغير الذي بين يديك. بل تخبر React بما يجب أن تكون عليه القيمة في العرض التالي. وحتى يحدث ذلك العرض، يظل العرض الحالي يرى القيمة القديمة.
افتح وحدة التحكم أسفل المعاينة وانقر مرة واحدة. يسجّل المعالج 0، القيمة القديمة، ثم يسجّل المكوّن عرضًا جديدًا بالقيمة 1. لا شيء خاطئ: count ثابت طوال العرض، والعرض التالي يحصل على ثابت جديد.
دوال التحديث
بسبب اللقطة، فإن استدعاء دالة الضبط ثلاث مرات بـ count + 1 يضيف واحدًا لا ثلاثة: كل استدعاء يحسب 0 + 1. مرّر دالة بدلًا من ذلك فتمرر React لكل استدعاء نتيجة الاستدعاء السابق.
استخدم صيغة دالة التحديث كلما اعتمدت القيمة التالية على السابقة وكان التحديث قد لا يعمل مباشرة من أحدث عرض: عدة عمليات ضبط في معالج واحد، أو دالة استدعاء لـ setInterval، أو تأثير.
الكائنات والمصفوفات في الحالة
تقرر React ما إذا كانت ستعيد العرض بمقارنة القيمة القديمة بالجديدة عبر Object.is. إذا غيّرت كائنًا في مكانه ومررت الكائن نفسه من جديد، تقول المقارنة إن شيئًا لم يتغير وتبقى الشاشة كما كانت. مرّر دائمًا كائنًا أو مصفوفة جديدة.
ينسخ { ...user, age: user.age + 1 } كل حقل ويتجاوز واحدًا. ويضيف [...tags, 'code'] عنصرًا، ويحذف filter عنصرًا، وكلاهما يُرجع مصفوفة جديدة. تتناول صفحة تحديث المصفوفات والكائنات الكائنات المتداخلة وتعديل عنصر واحد في قائمة والإدراج في الوسط.
الحالة الابتدائية الكسولة
يُقيَّم وسيط useState في كل عرض، رغم أن React لا تستخدمه إلا في المرة الأولى. عندما يكون حسابه مكلفًا (قراءة localStorage، أو تحليل نص كبير)، مرّر دالة. تستدعيها React مرة واحدة، في العرض الأول.
// Runs readSavedNotes() on every render, then throws the result away
const [notes, setNotes] = useState(readSavedNotes());
// Runs it once
const [notes, setNotes] = useState(readSavedNotes);
const [notes, setNotes] = useState(() => readSavedNotes());
إعادة ضبط الحالة بمفتاح
تخص الحالة موضع المكوّن في الشجرة. إذا عرضت key مختلفًا في الموضع نفسه، تعامله React كمكوّن جديد وتبدأ حالته من جديد. هذه أنظف طريقة لإعادة ضبط نموذج عندما يتغير الشيء الذي يعدّله.
اكتب رسالة، ثم بدّل المستلم: تُمحى المسودة. احذف key={to} وجرّب مرة أخرى، فيبقى النص، لأن React ترى الآن Draft نفسه في الموضع نفسه.
أخطاء شائعة
تسجيل الحالة مباشرة بعد ضبطها. ترى القيمة القديمة، كما هو موضح أعلاه. سجّل في جسم المكوّن، أو احسب القيمة التالية في متغير أولًا واستخدمه.
تعديل الحالة مباشرة. يمرر user.age++; setUser(user) الكائن نفسه، فلا يُعرض شيء. انسخ أولًا.
استدعاء دالة الضبط أثناء العرض. يستدعي <button onClick={setCount(count + 1)}> الدالة setCount أثناء العرض، فيعيد العرض، فيستدعيها من جديد. توقف React هذه الحلقة برسالة "Too many re-renders". مرّر دالة: onClick={() => setCount(count + 1)}.
تخزين قيم يمكنك حسابها. إذا كانت fullName دائمًا first + ' ' + last، فاحسبها أثناء العرض بدلًا من الاحتفاظ بقطعة ثالثة من الحالة قد تفقد التزامن.
الأسئلة الشائعة
ماذا يُرجع useState؟
مصفوفة فيها عنصران بالضبط: القيمة الحالية ودالة تستبدلها. تسمّيهما بنفسك بتفكيك المصفوفة، والاصطلاح هو [thing, setThing].
لماذا لا تتحدث حالتي فورًا؟
استدعاء دالة الضبط لا يغيّر المتغير الذي لديك بالفعل. بل يطلب من React عرض المكوّن من جديد، وتظهر القيمة الجديدة في ذلك العرض التالي. داخل معالج الحدث الحالي ما زالت القيمة القديمة في النطاق، فيطبع console.log(count) مباشرة بعد setCount(count + 1) الرقم القديم.
متى أمرر دالة إلى دالة الضبط؟
عندما تعتمد القيمة الجديدة على القديمة وتضبطها أكثر من مرة، أو من داخل مؤقت أو تأثير. يبدأ setCount(c => c + 1) دائمًا من أحدث قيمة، بينما يبدأ setCount(count + 1) من القيمة التي رآها هذا العرض.
هل يمكن لـ useState أن يحمل كائنًا أو مصفوفة؟
نعم، لكن عاملهما كقيم للقراءة فقط. ابنِ كائنًا أو مصفوفة جديدة ومررها إلى دالة الضبط (setUser({ ...user, name }) وsetItems([...items, item])). تغيير الموجود في مكانه لا يطلق أي عرض.
ما الفرق بين useState وuseReducer؟
كلاهما يخزن الحالة. يناسب useState بضع قيم مستقلة. أما useReducer فينقل منطق التحديث إلى دالة واحدة تستقبل إجراءً، وهذا أوضح عندما تغيّر أحداث كثيرة الحالة نفسها بطرق مترابطة.