حالة المكونات

من موسوعة حسوب


ماذا يفعل التابع setState؟

يُجدوِل التابع setState()‎ تحديثًا لكائن حالة المكوّن state. عندما تتغير الحالة يستجب المكوّن بإعادة التصيير.

ما الفرق بين الحالة state والخاصيّات props؟

الخاصيّات props (اختصارًا للكلمة properties) والحالة state كلاهما عبارة عن كائنات JavaScript مجرّدة. وفي حين أنّ كلاهما يحمل معلومات تؤثر في ناتج التصيير، فهما مختلفان بطريقة واحدة هامة، حيث تُمرَّر الخاصيّات إلى المكوّن (بشكل مماثل لمُعاملات الدالة) بينما تُدار الحالة state ضمن المكوّن (بشكل مشابه للمتغيرات المعرفة بداخل الدالة).

هنا تجد مصادر جيدة لقراءة المزيد حول استخدام الخاصيّات والحالة:

لماذا يُعطيني التابع setState القيمة الخاطئة؟

في React تُمثل this.props و this.state القيم المُصيَّرة، أي المعروضة على الشاشة حاليًّا.

تكون استدعاءات التابع setState غير متزامنة، فلا تعتمد على this.state لتعكس القيمة الجديدة للحالة بشكل مباشر بعد استدعاء التابع setState. مرر دالة تحديث بدلًا من تمرير كائن إن احتجت لحساب القيم بناءً على الحالة الحاليّة (انظر في الأسفل للمزيد من التفاصيل).

مثال عن شيفرة لن تعمل كما هو متوقع:

incrementCount() {
  // ملاحظة: لن يعمل هذا كما هو متوقع
  this.setState({count: this.state.count + 1});
}

handleSomething() {
  // فلنقل أنّ this.state.count تبدأ بالقيمة 0
  this.incrementCount();
  this.incrementCount();
  this.incrementCount();
  // عندما تعيد React تصيير المكون فستصبح قيمة this.state.count 1
  // ولكنك كنت تتوقعها أنها 3

  
  // هذا لأنّ الدالة incrementCount()
  // تقرأ this.state.count
  // ولكن لا تحدث React قيمتها حتى إعادة تصيير المكون
  // لذا ستقرأ React قيمة هذه الدالة على أنها صفر في كل مرة وستعينها للقيمة واحد

  // الحل موصوف لاحقًا
}

انظر في الأسفل لمعرفة كيفية إصلاح هذه المشكلة.

كيف أحدث الحالة بقيم تعتمد على الحالة الحالية؟

مرر دالة بدلًا من كائن إلى التابع setState لضمان استخدام الاستدعاء دائمًا لآخر إصدار من الحالة.

ما الفرق بين تمرير كائن أو دالة إلى التابع setState؟

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

incrementCount() {
  this.setState((prevState) => {
	// هام: اقرأ قيمة prevState بدلًا من this.state عند التحديث
    return {count: prevState.count + 1}
  });
}

handleSomething() {
  // فلنفترض أنّ قيمة this.state.count تبدأ من الصفر
  this.incrementCount();
  this.incrementCount();
  this.incrementCount();

  
  // إن قرأت قيمة this.state.count الآن فستكون لا زالت صفر
  // ولكن عندما يعاد تصيير المكون فستصبح 3
}

تعلم المزيد حول setState.

متى يكون التابع setState غير متزامن؟

حاليًّا التابع  setState غير متزامن بداخل معالج الأحداث.

إن كان المكوّن الأب Parent والمكوّن الابن Child يستدعيان التابع setState خلال حدث النقر يضمن لنا عدم التزامن ألّا يُعاد تصيير المكوّن الابن Child مرتين، وبدلًا من ذلك تمسح React تحديثات الحالة في نهاية أحداث المتصفّح. ينتج عن هذا تحسين هام في الأداء في التطبيقات الكبيرة.

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

لماذا لا تُحدِّث React قيمة this.state بشكلٍ متزامن؟

كما تحدثنا في القسم السابق، تنتظر React عن قصد حتى تستدعي جميع المكوّنات التابع setState()‎ في مُعالِجات أحداثها قبل البدء بإعادة التصيير. يُسرِّع هذا الأداء عن طريق تجنّب إعادة التصيير غير الضروريّة.

على أيّة حال قد لا تزال تتساءل لماذا لا تُحدِّث React قيمة this.state بشكل مباشر وبدون إعادة التصيير.

هنالك سببان رئيسيّان:

  • يخرق هذا التوافقيّة بين الخاصيّات props والحالة state، مسببًا مشاكل من الصعب تنقيحها.
  • سيجعل هذا من بعض الميّزات التي نعمل عليها مستحيلة التطبيق.

يشرح هذا التعليق في GitHub بالتفصيل أمثلة عن هذا.

هل ينبغي أن أستخدم مكتبات إدارة الحالة مثل Redux أو MobX؟

ربّما.

من الأفضل في البداية التعرّف على React أولًا قبل إضافة مكتبات أخرى. بإمكانك بناء تطبيقات معقدة وكبيرة باستخدام React فقط.

مصادر