تجعل النماذج الذهنية لـ React الإطار يبدو بسيطًا: تعلّم الأفكار الأساسية وراء المكونات، والعرض، والحالة، والآثار، ثم طبّقها لبناء واجهات بسرعة عبر الدردشة.

قد يبدو React محبطًا في البداية لأنك ترى واجهة المستخدم تتغير، لكنك لا تستطيع دائمًا تفسير لماذا تغيرت. تضغط زرًا، يحدث شيء، ثم جزء آخر من الصفحة يفاجئك. هذا عادة ليس "React غريب"، بل "صورة ما يجري في رأسي عن React ضبابية".
النموذج الذهني هو القصة البسيطة التي ترويها لنفسك عن كيفية عمل شيء ما. إذا كانت القصة خاطئة، ستتخذ قرارات واثقة تؤدي إلى نتائج مربكة. فكر في منظم الحرارة: نموذج سيء هو "أضبط 22°C، فتصبح الغرفة 22°C فورًا." نموذج أفضل هو "أضبط هدفًا، والمحبس يشغل ويطفئ التسخين مع الزمن للوصول إلى الهدف." مع القصة الأفضل، يتوقف السلوك عن الشعور بالعشوائية.
React يعمل بنفس الطريقة. بمجرد أن تتبنّى بعض الأفكار الواضحة، يصبح React متوقعًا: يمكنك النظر إلى البيانات الحالية وتخمين ما سيكون على الشاشة بثقة.
Dan Abramov ساهم في ترويج عقلية "اجعلها متوقعة". الهدف ليس حفظ قواعد، بل الاحتفاظ بمجموعة صغيرة من الحقائق في رأسك حتى تتمكن من التصحيح عن طريق التفكير، لا بالتجربة والخطأ.
احتفظ بهذه الأفكار في ذهنك:
تمسّك بهذه الأفكار ويتوقف React عن الشعور بالسحر. سيصبح نظامًا يمكنك الوثوق به.
يصبح React أسهل عندما تتوقف عن التفكير في "الشاشات" وتبدأ بالتفكير في قطع صغيرة. المكون هو وحدة واجهة قابلة لإعادة الاستخدام. يأخذ مدخلات ويعيد وصفًا لما يجب أن تبدو عليه الواجهة لهذه المدخلات.
يساعد التعامل مع المكون كوصْف نقي: "بناءً على هذه البيانات، أعرض هذا." يمكن استخدام هذا الوصف في أماكن كثيرة لأنه لا يعتمد على مكان وجوده.
الـ props هي المدخلات. تأتي من المكون الأب. الـ props ليست "مملوكة" للمكون، وليست شيئًا يجب أن يغيّره المكون بصمت. إذا استقبل زر label="Save"، فمهمة الزر أن يعرض ذلك الوسم، لا أن يقرر أنه يجب أن يكون مختلفًا.
الحالة (state) هي بيانات مملوكة. هي ما يتذكره المكون مع الزمن. تتغير الحالة عندما يتفاعل المستخدم، عندما ينتهي طلب، أو عندما تقرر أن شيئًا يجب أن يكون مختلفًا. على عكس props، الحالة تنتمي لذلك المكون (أو لأي مكون تختار أن يملكها).
نسخة مبسطة للفكرة الأساسية: الواجهة دالة للحالة. إذا قالت الحالة "loading"، اعرض مؤشر تحميل. إذا قالت "error"، اعرض رسالة. إذا قالت "items = 3"، اعرض ثلاثة صفوف. وظيفتك أن تحافظ على أن الواجهة تقرأ من الحالة، لا تنجرف إلى متغيرات مخفية.
طريقة سريعة للفصل بين المفاهيم:
SearchBox, ProfileCard, CheckoutForm)name, price, disabled)isOpen, query, selectedId)مثال: نافذة منبثقة (modal). يمكن للأب تمرير title و onClose كـ props. قد يملك المودال isAnimating كحالة.
حتى لو كنت تولّد واجهة عبر الدردشة (مثلًا على Koder.ai)، يبقى هذا الفصل أسرع طريقة للبقاء هادئًا: قرّر ما هو props مقابل state أولًا، ثم دع الواجهة تتبع ذلك.
طريقة مفيدة لحمل React في رأسك (بروح Dan Abramov) هي: العرض هو حساب، وليس وظيفة طلاء. React تشغّل دوال مكوناتك لتعرف كيف يجب أن تبدو الواجهة للـ props و state الحالية. الناتج هو وصف للواجهة، ليس بكسلات.
إعادة العرض تعني فقط أن React تكرر هذا الحساب. هذا لا يعني "إعادة رسم الصفحة كلها." React تقارن النتيجة الجديدة مع السابقة وتطبّق أصغر مجموعة من التغييرات على DOM الحقيقي. قد تُعاد عدة مكونات للعرض بينما تُحدّث بضعة عقد DOM فقط.
معظم عمليات إعادة العرض تحدث لأسباب بسيطة: تغيّرت حالة مكون، تغيّرت props، أو أعاد الأب العرض وطلب من الابن أن يعرض مجددًا. هذا الأخير يفاجئ الناس غالبًا، لكنه عادةً مقبول. إذا تعاملت مع العرض على أنه "رخيص وممل"، سيصبح تطبيقك أسهل في الفهم.
قاعدة عملية تحافظ على هذا بسيطًا: اجعل دالة العرض نقية. بالنسبة لنفس المدخلات (props + state)، يجب أن يرجع المكون نفس وصف الواجهة. أبقِ المفاجآت خارج العرض.
مثال ملموس: إذا ولّدت معرفًا بـ Math.random() داخل العرض، فإن إعادة العرض تغيّره وفجأة يفقد مربع اختيار التركيز أو يُعاد تركيب عنصر القائمة. أنشئ المعرف مرة واحدة (في state، أو memo، أو خارج المكون) وسيصبح العرض ثابتًا.
إذا تذكرت جملة واحدة: إعادة العرض تعني "إعادة حساب ما يجب أن تكون عليه الواجهة"، لا "إعادة بناء كل شيء".
نموذج مفيد آخر: تحديثات الحالة هي طلبات، ليست تعيينات فورية. عندما تستدعي مُحدّثًا مثل setCount(count + 1), فأنت تطلب من React جدولة عرض بقيمة جديدة. إذا قرأت الحالة فورًا بعد ذلك، قد ترى القيمة القديمة لأن React لم تعرض بعد.
لهذا السبب تهم التحديثات "الصغيرة والمتوقعة". فضّل وصف التغيير بدلًا من أخذ ما تظن أنه القيمة الحالية. عندما تعتمد القيمة التالية على السابقة، استخدم صيغة المحدث: setCount(c => c + 1). هذا يطابق طريقة عمل React: قد تُدرج تحديثات متعددة في الطابور ثم تُطبّق بالترتيب.
اللا-تغيّر (immutability) هو النصف الآخر من الصورة. لا تغيّر الكائنات والمصفوفات في مكانها. أنشئ واحدًا جديدًا بالتغيير. حينها تلاحظ React أن "هذه القيمة جديدة"، ويمكن لعقلك تتبّع ما تغيّر.
مثال: تبديل حالة مهمة (todo). النهج الآمن هو إنشاء مصفوفة جديدة وكائن todo جديد للعنصر الذي غيّرته. النهج المحفوف بالمخاطر هو قلب todo.done = !todo.done داخل المصفوفة الحالية.
أيضًا اجعل الحالة مُصغّرة. فخ شائع هو تخزين قيم يمكنك حسابها. إذا كان لديك بالفعل items و filter, لا تخزن filteredItems في الحالة. احسبها أثناء العرض. عدد أقل من متغيرات الحالة يعني طرقًا أقل لانحراف القيم.
اختبار بسيط لما يجب أن يكون في الحالة:
إذا كنت تبني واجهة عبر الدردشة (بما في ذلك على Koder.ai)، اطلب تغييرات كرقع صغيرة: "أضف علمًا بولينيًا واحدًا" أو "حدّث هذه القائمة بشكل لا متغيّر". التغييرات الصغيرة والواضحة تبقي المولد وكود React متوافقين.
العرض يصف الواجهة. الآثار تزامن مع العالم الخارجي. "الخارج" يعني الأشياء التي لا تسيطر عليها React: طلبات الشبكة، مؤقتات، APIs المتصفح، وأحيانًا عمل DOM إجرائي.
إذا كان يمكن حساب شيء من props و state، فعادةً لا ينبغي أن يعيش في effect. وضعه في effect يضيف خطوة ثانية (عرض، تشغيل effect، تعيين حالة، عرض مرة أخرى). هذه القفزة الإضافية هي حيث تظهر الوميض، الحلقات، ومشاكل "لماذا هذا قديم؟".
التشتت الشائع: لديك firstName و lastName, وتخزن fullName في الحالة باستخدام effect. لكن fullName ليست أثرًا جانبيًا. هي بيانات مشتقة. احسبها أثناء العرض وستطابق دائمًا.
كقاعدة: استخرج قيم الواجهة أثناء العرض (أو استخدم useMemo إذا كان الحساب مكلفًا فعلاً)، واستخدم الآثار لـ "افعل شيئًا"، لا لـ "اكتشف شيئًا".
عامل مصفوفة الاعتماديات كقائمة: "عندما تتغير هذه القيم، أعِد المزامنة مع الخارج." ليست خدعة أداء وليست مكانًا لإسكات التحذيرات.
مثال: إذا جلبت تفاصيل المستخدم عندما يتغير userId, فيجب أن يكون userId في مصفوفة الاعتماديات لأنه يجب أن يشغّل المزامنة. إذا كان الـ effect يستخدم token أيضًا، أدرجه، وإلا قد تجلب ببيانات اعتماد قديمة.
فحص حدسي جيد: إذا كان حذف effect سيجعل الواجهة خاطئة فقط، فربما لم يكن effect حقيقيًا. إذا كان حذفه سيوقف مؤقتًا، إلغاء اشتراك، أو تفصيل طلب، فربما كان effect صائبًا.
واحد من أهم النماذج الذهنية المفيدة بسيط: البيانات تتجه للأسفل على الشجرة، وأفعال المستخدم تتجه للأعلى.
يمرر الأب القيم إلى الأبناء. لا ينبغي للأبناء أن "يملكوا" نفس القيمة في مكانين. يطلبون التغييرات عن طريق استدعاء دالة، ويقرر الأب ما هي القيمة الجديدة.
عندما يجب أن يتفق جزءان من الواجهة، اختر مكانًا واحدًا لتخزين القيمة، ثم مرّرها لأسفل. هذا ما يسمى "رفع الحالة". قد يبدو كأنبوب إضافي، لكنه يمنع مشكلة أسوأ: حالتان تنحرفان وتضطر لإضافة حيل لمزامنتهما.
مثال: صندوق بحث وقائمة النتائج. إذا خزن الحقل استعلامه داخليًا والقائمة خزنت استعلامها داخليًا أيضًا، ستصل لنقطة ترى "الحقل يعرض X لكن القائمة تستخدم Y." الحل هو الاحتفاظ بـ query في أب، تمرّره إلى الاثنين، وتمرّر onChangeQuery(newValue) للحقل.
رفع الحالة ليس دائمًا الحل. إذا كانت قيمة تهم مكوّنًا واحدًا فقط، احتفظ بها هناك. إبقاء الحالة قريبة من مكان استخدامها يجعل الكود أسهل للقراءة.
حد عملي:
إذا لم تكن متأكدًا إن كانت ترفع الحالة، ابحث عن إشارات مثل: عرضان يوضحان نفس القيمة بطرق مختلفة؛ حدث في مكان واحد يجب أن يحدث تغييرًا بعيدًا؛ تكرر نسخ props إلى state "تحسبًا"؛ أو تضيف آثارًا فقط لمزامنة قيمتين.
هذا النموذج يساعد أيضًا عند البناء عبر أدوات الدردشة مثل Koder.ai: اطلب مالكًا واحدًا لكل قطعة من الحالة المشتركة، ثم ولّد معالجات تتدفق للأعلى.
اختر ميزة صغيرة يمكن الإمساك بها ذهنيًا. مثال جيد هو قائمة قابلة للبحث حيث يمكنك النقر على عنصر لرؤية تفاصيل في نافذة منبثقة.
ابدأ بتخطيط أجزاء الواجهة والأحداث الممكنة. لا تفكر في الكود بعد. فكر فيما يمكن للمستخدم فعله ورؤيته: هناك حقل بحث، قائمة، تمييز صف محدد، ومودال. الأحداث هي الكتابة في البحث، النقر على عنصر، فتح المودال، وإغلاق المودال.
الآن "ارسم الحالة." اكتب القيم القليلة التي يجب أن تُخزن، وقرر من يملكها. قاعدة بسيطة تعمل جيدًا: أقرب أب مشترك لكل الأماكن التي تحتاج القيمة يجب أن يملكها.
لهذه الميزة، الحالة المخزنة يمكن أن تكون صغيرة: query (سلسلة)، selectedId (معرّف أو null)، و isModalOpen (قيمة منطقية). القائمة تقرأ query وتعرض العناصر. المودال يقرأ selectedId ليعرض التفاصيل. إذا احتاجت كل من القائمة والمودال selectedId، احتفظ به في الأب، لا في كلاهما.
بعد ذلك، فصل البيانات المشتقة عن المخزنة. القائمة المصفاة مشتقّة: filteredItems = items.filter(...). لا تخزنها في الحالة لأنها دائمًا قابلة لإعادة الحساب من items و query. تخزين البيانات المشتقة هو كيفية انحراف القيم.
عندها فقط اسأل: هل نحتاج effect؟ إذا كانت العناصر في الذاكرة بالفعل، لا. إذا كان كتابة استعلام يجب أن تجلب نتائج، نعم. إذا كان إغلاق المودال يجب أن يحفظ شيئًا، نعم. الآثار للمزامنة (جلب، حفظ، اشتراك)، لا لتوصيل الأسلاك الأساسية للواجهة.
أخيرًا، اختبر التدفق ببعض الحالات الحافة:
selectedId صالحًا؟إذا كنت تستطيع الإجابة على هذه على الورق، فعادةً ما يكون كود React بسيطًا.
معظم الالتباس في React ليس متعلقًا بالتركيب النحوي. يحدث عندما يتوقف كودك عن مطابقة القصة البسيطة في رأسك.
تخزين حالة مشتقة. تحفظ fullName في الحالة رغم أنه مجرد firstName + lastName. يعمل حتى يتغير حقل واحد ولا يتغير الآخر، وتعرض الواجهة قيمة قديمة.
حلقات الآثار. effect يجلب بيانات، يضبط حالة، وقائمة الاعتماديات تجعله يعمل مجددًا. العرض هو طلبات متكررة، واجهة مهتزة، أو حالة لا تستقر.
الإغلاقات القديمة (stale closures). معالج نقرة يقرأ قيمة قديمة (مثل عدّاد أو فلتر قديم). العرض: "نقرّت لكنه استخدم قيمة البارحة."
الحالة العالمية في كل مكان. وضع كل تفاصيل الواجهة في مخزن عام يجعل من الصعب معرفة من يملك ماذا. العرض: تغيّر شيء واحد فتتفاعل ثلاث شاشات بطرق مفاجئة.
تغيير كائنات متداخلة مكانياً. تحدّث كائنًا أو مصفوفة في مكانها وتتساءل لماذا لم تحدث إعادة عرض. العرض: "البيانات تغيّرت لكن لا شيء أعيد عرضه."
مثال ملموس: لوحة "بحث و ترتيب" لقائمة. إذا خزنت filteredItems في الحالة، فقد تنحرف عن items عند وصول بيانات جديدة. بدلًا من ذلك خزّن المدخلات (نص البحث، خيار الترتيب) واحتسب القائمة المفلترة أثناء العرض.
مع الآثار، احتفظ بها للمزامنة مع العالم الخارجي (جلب، اشتراكات، مؤقتات). إذا كان effect يقوم بعمل واجهة أساسية، غالبًا ما ينتمي ذلك إلى العرض أو معالج حدث.
عند التوليد أو تعديل الكود عبر الدردشة، تظهر هذه الأخطاء أسرع لأن التغييرات قد تصل كقطع كبيرة. عادةً ما يكون من الجيد طلب التغييرات بصيغة ملكية: "ما هو مصدر الحقيقة لهذه القيمة؟" و"هل يمكننا حساب هذا بدلًا من تخزينه؟"
عندما تبدأ واجهتك بالشعور بعدم التوقع، نادراً ما يكون السبب "كثرة React." عادةً تكون الحالة كثيرة جدًا، في الأماكن الخطأ، وتقوم بأدوار لا ينبغي لها القيام بها.
قبل أن تضيف useState آخر، توقف واسأل:
مثال صغير: صندوق بحث، قائمة فلتر، قائمة. إذا خزنت كلاً من query و filteredItems في الحالة، فهناك الآن مصدران للحقيقة. بدلًا من ذلك احتفظ بـ query و filter في الحالة، ثم استخرج filteredItems أثناء العرض من القائمة الكاملة.
هذا مهم أيضًا عند البناء بسرعة عبر أدوات الدردشة. السرعة رائعة، لكن استمر بسؤال: "هل أضفنا حالة، أم أضفنا قيمة مشتقة بالخطأ؟" إذا كانت مشتقة، احذف تلك الحالة واحسبها.
فريق صغير يبني واجهة إدارة: جدول طلبات، بعض الفلاتر، وحوار لتحرير طلب. الطلب الأولي غامض: "أضف فلاتر ونوافذ تحرير." قد يبدو بسيطًا، لكنه غالبًا يتحول إلى حالة مبعثرة في كل مكان.
اجعله ملموسًا بترجمة الطلب إلى حالة وأحداث. بدلًا من "فلاتر"، سمّ الحالة: query, status, dateRange. بدلًا من "نافذة تحرير"، سم الحدث: "المستخدم يضغط Edit على صف". ثم قرر من يملك كل جزء من الحالة (الصفحة، الجدول، أو الحوار) وما يمكن اشتقاقه (مثل قائمة مفلترة).
نصوص توجيهية تحافظ على النموذج سليمًا (تعمل جيدًا مع منشئي الدردشة مثل Koder.ai):
OrdersPage يملك filters و selectedOrderId. OrdersTable يتحكم به filters وينادي onEdit(orderId)."visibleOrders من orders و filters. لا تخزن visibleOrders في الحالة."EditOrderDialog يستقبل order و open. عند الحفظ، نادِ onSave(updatedOrder) ثم إغلق."filters مع الـ URL، لا لحساب الصفوف المفلترة."بعد توليد الواجهة أو تعديلها، راجع التغييرات بقائمة فحص سريعة: كل قيمة حالة لها مالك واحد، القيم المشتقة ليست مخزنة، الآثار للمزامنة مع الخارج فقط (URL، شبكة، تخزين)، والأحداث تتدفق لأسفل كـ props وللأعلى كاستدعاءات رد النداء.
عندما تكون الحالة متوقعة، يصبح التكرار آمنًا. يمكنك تغيير تخطيط الجدول، إضافة فلتر جديد، أو تعديل حقول الحوار دون التخمين أي حالة مخفية ستنكسر.
السرعة مفيدة فقط إذا ظل التطبيق سهل الفهم. أبسط وسيلة حماية هي التعامل مع هذه النماذج الذهنية كقائمة فحص تطبقها قبل كتابة (أو توليد) الواجهة.
ابدأ كل ميزة بنفس الطريقة: اكتب الحالة التي تحتاجها، الأحداث التي يمكن أن تغيّرها، ومن يملكها. إذا لم تستطع أن تقول "هذا المكون يملك هذه الحالة، وهذه الأحداث تحدثها"، فربما سينتهي بك الأمر بحالة مبعثرة وإعادة عروض مفاجئة.
إذا كنت تبني عبر الدردشة، ابدأ بوضع التخطيط. اوصف المكونات، شكل الحالة، والانتقالات بلغة بسيطة قبل طلب الكود. مثال: "لوحة فلتر تحدّث حالة query؛ قائمة النتائج تُستنتج من query؛ اختيار عنصر يضبط selectedId؛ الإغلاق يمسحه." عندما يقرأ ذلك بسلاسة، يصبح توليد الواجهة خطوة ميكانيكية.
إذا كنت تستخدم Koder.ai (koder.ai) لتوليد كود React، يجدر بك عمل فحص سريع قبل المضي: مالك واضح لكل قيمة حالة، الواجهة مستنتَجة من الحالة، الآثار فقط للمزامنة، ولا مصادر متكررة للحقيقة.
ثم كرّر بخطوات صغيرة. إذا أردت تغيير شكل الحالة (مثل الانتقال من عدة boolean إلى حقل status واحد)، خذ لقطة أولًا، جرّب، وارجع إذا ساءت القصة الذهنية. وعند الحاجة لمراجعة أعمق أو تسليم، تصدير الشيفرة المصدرية يسهل الإجابة على السؤال الحقيقي: هل لا تزال بنية الحالة تروي قصة الواجهة؟
نموذج بداية جيد هو: UI = f(state, props). مكوناتك لا "تعدّل DOM"؛ هي تصف ما يجب أن يظهر على الشاشة بناءً على البيانات الحالية. إذا بدت الشاشة خاطئة، افحص الحالة/الـ props التي أنتجتها، لا الـ DOM.
الـ Props هي مدخلات من الأب؛ يجب أن يتعامل المكون معها كقيم للقراءة فقط. الحالة (state) هي ذاكرة يملكها المكون (أو المكون الذي تختاره كمالك). إذا كانت قيمة يجب مشاركتها، ارفعها لأعلى ومرّرها كـ props.
إعادة العرض تعني أن React تعيد تشغيل دالة المكون لحساب وصف الواجهة التالي. هذا لا يعني بالضرورة أن كل الصفحة أعيدت رسمها. بعد ذلك تقوم React بتحديث DOM الحقيقي بأقل مجموعة من التغييرات اللازمة.
لأن تحديثات الحالة تُجدول وليست تعيينات فورية. إذا كانت القيمة التالية تعتمد على السابقة، استخدم الشكل المحدث للـ setter لكي لا تعتمد على قيمة قديمة:
setCount(c => c + 1)هذا يبقى صحيحًا حتى لو تم إدراج تحديثات متعددة في الطابور.
تجنّب تخزين أي شيء يمكنك حسابه من المدخلات الحالية. خزّن المدخلات واستخرج الباقي أثناء العرض.
أمثلة:
items, filtervisibleItems = items.filter(...)هذا يمنع القيم من الانحراف عن بعضها.
استخدم الآثار (effects) لمزامنة ما لا تسيطر عليه React: جلب بيانات، اشتراكات، مؤقتات، APIs المتصفح، أو عمل DOM متعمد.
لا تستخدم الـ effect فقط لحساب قيم واجهة المستخدم من الحالة—احسبها أثناء العرض (أو استخدم useMemo إذا كان مكلفًا فعلاً).
عامل مصفوفة الاعتماديات كـ: "عندما تتغير هذه القيم، أعيد المزامنة مع الخارج." أدرج كل قيمة تفاعلية يقرأها الـ effect.
إذا تركت شيئًا خارِجًا، قد تستخدم بيانات قديمة (مثل userId أو رمز قديم). إذا أضفت أشياء خاطئة، قد تخلق حلقات—غالبًا علامة أن العمل يجب أن يكون في معالجات أو في العرض.
إذا كان جزآن من الواجهة يجب أن يتفقا دائمًا، ضع الحالة في أقرب أب مشترك، مرّر القيمة لأسفل، ومرّر المعالجات للأعلى.
اختبار سريع: إذا كنت تكرر نفس القيمة في مكوّنين وتكتب آثارًا لمزامنتهما، فالأرجح أن هذه الحالة تحتاج مالكًا واحدًا.
يحدث ذلك عندما يلتقط المعالج قيمة قديمة من رندر سابق. إصلاحات شائعة:
setX(prev => ...)إذا نقرت وزُخدمت قيمة "أمس"، فاشتبِه في إغلاق قديم (stale closure).
ابدأ بخطة صغيرة: المكونات، مالكو الحالة، والأحداث. ثم ولّد الكود كـ تصحيحات صغيرة (اضافة حقل حالة واحد، إضافة معالج واحد، استنتاج قيمة واحدة) بدلًا من إعادة كتابة كبيرة.
إذا استخدمت منشئ دردشة مثل Koder.ai، اطلب:
هذا يبقي الكود المولّد متوافقًا مع نموذج React الذهني.