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

الحالة هي أي بيانات يمكن أن تتغير أثناء تشغيل التطبيق. هذا يشمل ما تراه (مودال مفتوح)، ما تقوم بتعديله (مسودة نموذج)، والبيانات التي تسترجعها (قائمة مشاريع). المشكلة أن كل هذه تُسمى "حالة" على الرغم من أنها تتصرف بشكل مختلف جدًا.
معظم التطبيقات الفوضوية تنهار بنفس الطريقة: اختلاط أنواع كثيرة من الحالة في نفس المكان. يصبح المكوّن محتفظًا ببيانات من الخادم، وآلات واجهة (UI) بعلامات، ومسودات نماذج، وقيم مشتقة، ثم يحاول مزامنتها باستخدام التأثيرات. قبل أن تدري، لا يمكنك الإجابة على أسئلة بسيطة مثل "من أين جاءت هذه القيمة؟" أو "ما الذي يحدثها؟" دون البحث في عدة ملفات.
التطبيقات المولّدة تنجرف نحو هذا أسرع لأن من السهل قبول النسخة الأولى العاملة. تضيف شاشة جديدة، تنسخ نمطًا، تصلح خطأً بـ useEffect آخر، والآن لديك مصدران للحقيقة. إذا غير المولّد أو الفريق الاتجاه في منتصف الطريق (حالة محلية هنا، مخزن عام هناك)، يتجمع في قاعدة الشيفرة أنماط بدلًا من البناء على واحد.
الهدف ممل: أنواع أقل من الحالة، وأماكن أقل للنظر فيها. عندما يوجد منزل واضح واحد لبيانات الخادم ومنزل واضح واحد لحالة واجهة المستخدم فقط، تصبح الأخطاء أصغر وتتوقف التغييرات عن أن تكون محفوفة بالمخاطر.
"اجعلها روتينية" تعني التزامك ببعض القواعد:
مثال ملموس: إذا كانت قائمة المستخدمين تأتي من الخادم، عاملها كحالة خادم واسترجعها حيث تُستخدم. إذا كان selectedUserId موجودًا فقط للتحكم بلوحة التفاصيل، احتفظ به كحالة واجهة صغيرة بالقرب من تلك اللوحة. خلط هذين هو كيف يبدأ التعقيد.
معظم مشاكل حالة React تبدأ بخطأ واحد: معاملة بيانات الخادم كحالة واجهة. فرّق بينهما مبكرًا وستبقى إدارة الحالة هادئة حتى مع نمو التطبيق.
حالة الخادم تنتمي إلى الباكند: المستخدمون، الطلبات، المهام، الصلاحيات، الأسعار، أعلام الميزات. يمكن أن تتغير دون تدخل تطبيقك (علامة تبويب أخرى تحدثها، مسؤول يعدّلها، مهمة مجدولة تعمل، انتهاء صلاحية البيانات). لأنها مشتركة وقابلة للتغيير، تحتاج إلى جلب، تخزين مؤقت، إعادة جلب، ومعالجة أخطاء.
حالة العميل هي ما تهتم به واجهتك الآن فقط: أي مودال مفتوح، أي تبويب محدد، تبديل فلتر، ترتيب فرز، شريط جانبي مطوي، مسودة بحث. إذا أغلقت التبويب، فمقبول أن تفقدها.
اختبار سريع: "هل بإمكاني تحديث الصفحة وإعادة بناء هذا من الخادم؟"
هناك أيضًا الحالة المشتقة، التي تنقذك من إنشاء حالة إضافية في المقام الأول. هي قيمة يمكنك حسابها من قيم أخرى، لذا لا تخزنها. القوائم المصفاة، المجاميع، isFormValid، و"عرض حالة الفراغ" عادةً تنتمي هنا.
مثال: تسترجع قائمة مشاريع (حالة خادم). الفلتر المحدد وعلم فتح مربع "مشروع جديد" هما حالة عميل. القائمة المرئية بعد الفلترة هي حالة مشتقة. إذا خزنت القائمة المرئية بشكل منفصل، ستبتعد عن المزامنة وستطارد أخطاء "لماذا هي قديمة؟".
يساعد هذا الفصل عندما تولد أداة مثل Koder.ai الشاشات بسرعة: احتفظ ببيانات الباكند في طبقة جلب واحدة، ابقي اختيارات الواجهة قريبة من المكونات، وتجنب تخزين القيم المحسوبة.
تصبح الحالة مؤلمة عندما يمتلك قطعتان من البيانات مالكان. أسرع طريقة للحفاظ على البساطة هي تحديد من يملك ماذا والالتزام بذلك.
مثال: تسترجع قائمة مستخدمين وتعرض تفاصيل عند اختيار واحد. خطأ شائع هو تخزين كائن المستخدم المحدد بالكامل في الحالة. خزّن selectedUserId بدلًا من ذلك. احتفظ بالقائمة في كاش الخادم. عرض التفاصيل يبحث عن المستخدم بالمعرف، لذا تحديثات الاسترجاع تعمل بدون شيفرة مزامنة إضافية.
في التطبيقات المولّدة، من السهل أيضًا قبول حالة مولّدة "مفيدة" تُكرر بيانات الخادم. عندما ترى كودًا يفعل fetch -> setState -> تعديل -> إعادة جلب، توقف. غالبًا ما يكون ذلك علامة أنك تبني قاعدة بيانات ثانية في المتصفح.
حالة الخادم هي أي شيء يعيش على الباكند: القوائم، صفحات التفاصيل، نتائج البحث، الصلاحيات، الأعداد. النهج الممل هو اختيار أداة واحدة لها والالتزام بها. بالنسبة لكثير من تطبيقات React، TanStack Query تكفي.
الهدف واضح: المكونات تطلب البيانات، تعرض حالات التحميل/الخطأ، ولا تهتم بعدد استدعاءات الجلب تحت الغطاء. هذا مهم في التطبيقات المولّدة لأن التباينات الصغيرة تتضاعف بسرعة مع إضافة شاشات جديدة.
عامل مفاتيح الاستعلام كمُسمّى، لا كتفصيلة لاحقة. اجعلها متسقة: مفاتيح مصفوفية ثابتة، تضمّن فقط المدخلات التي تغير النتيجة (الفلاتر، الصفحة، الفرز)، وفضل أشكالًا متوقعة قليلة على الكثير من الحالات الخاصة. كثير من الفرق يضع بناء المفاتيح في مساعدات صغيرة حتى تتبع كل شاشة نفس القواعد.
بالنسبة للكتابة، استخدم mutations مع معالجة نجاح صريحة. يجب أن تجيب الـ mutation على سؤالين: ماذا تغيّر؟ وماذا يجب أن تفعل الواجهة بعد ذلك؟
مثال: تنشئ مهمة جديدة. عند النجاح، إما أن تُبطل استعلام قائمة المهام (فيعيد تحميلًا مرة واحدة) أو تقوم بتحديث الكاش المستهدف (إضافة المهمة الجديدة إلى القائمة المخبأة). اختر نهجًا واحدًا لكل ميزة والتزم به.
إذا شعرت بالإغراء لإضافة استدعاءات إعادة جلب في أماكن متعددة "فقط للسلامة"، اختر حركة مملة واحدة بدلًا من ذلك:
حالة العميل هي الأشياء التي يملكها المتصفح: علم فتح شريط جانبي، صف محدد، نص فلتر، مسودة قبل الحفظ. احتفظ بها قريبة من مكان استخدامها وعادةً ستبقى قابلة للإدارة.
ابدأ صغيرًا: useState في أقرب مكوّن. عند توليد الشاشات (مثلًا مع Koder.ai)، من المغري دفع كل شيء إلى مخزن عام "للمستقبل". هكذا ينتهي بك الأمر بمخزن لا يفهمه أحد.
حرك الحالة للأعلى فقط عندما تستطيع تسمية مشكلة المشاركة.
مثال: جدول مع لوحة تفاصيل يمكن أن يحتفظ بـ selectedRowId داخل مكوّن الجدول. إذا كانت شريط أدوات في جزء آخر من الصفحة يحتاجها أيضًا، ارفعها إلى مكوّن الصفحة. إذا احتاجتها مسارات منفصلة (مثل تحرير جماعي)، حينها يكون مخزن صغير منطقيًا.
إذا استخدمت مخزنًا (Zustand أو ما شابه)، اجعله مركزًا على مهمة واحدة. خزن "ماذا" (معرّفات مختارة، فلاتر)، لا "نتائج" (قوائم مرتبة) التي يمكنك اشتقاقها.
عندما يبدأ المخزن بالنمو، اسأل: هل هذه ميزة واحدة حقًا؟ إذا كانت الإجابة "نوعًا ما"، فقم بتقسيمها الآن قبل أن يحولها الميزة التالية إلى كرة من الحالة تخاف لمسها.
غالبًا ما تأتي أخطاء النماذج من خلط ثلاثة أشياء: ما يكتبه المستخدم، ما حفظه الخادم، وما تعرضه الواجهة.
لإدارة مملة، عامل النموذج كحالة عميل حتى تقوم بالإرسال. بيانات الخادم هي النسخة المحفوظة الأخيرة. النموذج هو مسودة. لا تعدّل كائن الخادم في مكانه. انسخ القيم إلى حالة مسودة، اترك المستخدم يغيّرها بحرية، ثم أرسل وأعد الاسترجاع (أو حدّث الكاش) عند النجاح.
قرر مبكرًا ما الذي يجب أن يبقى عندما يغادر المستخدم الصفحة. هذا الاختيار الواحِد يمنع الكثير من أخطاء المفاجأة. على سبيل المثال، وضع التحرير الداخلي والقوائم المنسدلة المفتوحة عادةً يجب أن تُعاد إلى الوضع الافتراضي، بينما مسودة معالج طويل أو رسالة غير مُرسلة قد تُحفظ. احتفظ بالاستمرارية عبر إعادة التحميل فقط عندما يتوقع المستخدمون ذلك بوضوح (مثل عملية الخروج).
احتفظ بقواعد التحقق في مكان واحد. إذا نشرت القواعد عبر المدخلات، ومعالجات الإرسال، والمساعدات، ستنتهي بأخطاء متناقضة. فضّل مخطط واحد (أو دالة validate() واحدة)، ودع الواجهة تقرر متى تظهر الأخطاء (عند التغيير، عند الخروج من الحقل، أو عند الإرسال).
مثال: تولّد شاشة "تعديل الملف الشخصي" في Koder.ai. حمّل الملف المحفوظ كحالة خادم. أنشئ حالة مسودة لحقول النموذج. عرض "تغييرات غير محفوظة" عن طريق مقارنة المسودة مع المحفوظ. إذا ألغي المستخدم، تخلص من المسودة وأعد عرض النسخة الخادمة. إذا حفظ، أرسل المسودة ثم استبدل النسخة المحفوظة برد الخادم.
كلما نما تطبيق مولّد، من الشائع أن ينتهي بك الأمر بنفس البيانات في ثلاثة أماكن: حالة المكوّن، مخزن عام، وكاش. غالبًا ما لا يكون الحل مكتبة جديدة. الحل هو اختيار منزل واحد لكل قطعة حالة.
تدفق تنظيف يعمل في معظم التطبيقات:
filteredUsers إذا يمكنك حسابها من users + filter. فضّل selectedUserId على selectedUser المكرر.مثال: تطبيق CRUD مولّد من Koder.ai غالبًا يبدأ بـ useEffect للجلب بالإضافة إلى نسخة مخزنة في مخزن عام من نفس القائمة. بعد توحيد حالة الخادم، تصبح القائمة من استعلام واحد، و"التحديث" يتحول إلى إبطال بدلاً من مزامنة يدوية.
لأسماء متسقة ومملة:
users.list, users.detail(id)ui.isCreateModalOpen, filters.userSearchopenCreateModal(), setUserSearch(value)users.create, users.update, users.deleteالهدف هو مصدر واحد للحق لكل شيء، مع حدود واضحة بين حالة الخادم وحالة العميل.
تبدأ مشاكل الحالة صغيرة، ثم يومًا ما تغير حقلًا وتختلف ثلاثة أجزاء من الواجهة حول القيمة "الحقيقية".
أوضح علامة تحذير هي البيانات المكررة: نفس المستخدم أو السلة موجودة في مكوّن، مخزن عام، وكاش للطلبات. كل نسخة تحدث في وقت مختلف، وتضيف مزيدًا من الشيفرة فقط للحفاظ على تساويها.
علامة أخرى هي شيفرة المزامنة: تأثيرات تدفع الحالة ذهابًا وإيابًا. أنماط مثل "عندما يتغير استعلام، حدّث المخزن" و"عندما يتغير المخزن، أعد الاسترجاع" قد تعمل حتى تظهر حالة حافة تسبب قيمًا قديمة أو حلقات.
بعض الأعلام الحمراء السريعة:
needsRefresh, didInit, isSaving لا يحذفها أحد.مثال: تولّد لوحة تحكم في Koder.ai وتضيف مودال تحرير الملف الشخصي. إذا كانت بيانات الملف الشخصي مخزنة في كاش الاستعلام، ونسخت إلى مخزن عام، وتكررت في حالة نموذج محلية، أصبح لديك ثلاث مصادر للحقيقة. في المرة الأولى التي تضيف فيها إعادة استرجاع خلفية أو تحديثات متفائلة، ستظهر التناقضات.
عندما ترى هذه العلامات، الحركة المملة هي اختيار مالك واحد لكل قطعة بيانات وحذف النسخ المرآتية.
تخزين الأشياء "للمستقبل" هو واحد من أسرع الطرق لجعل الحالة مؤلمة، خاصة في التطبيقات المولّدة.
نسخ استجابات API إلى مخزن عام هو فخ شائع. إذا كانت البيانات تأتي من الخادم (قوائم، تفاصيل، ملف المستخدم)، لا تنسخها إلى مخزن العميل افتراضيًا. اختر منزلًا واحدًا لبيانات الخادم (عادةً كاش الاستعلام). استخدم مخزن العميل للقيم الخاصة بالواجهة التي لا يعرفها الخادم.
تخزين القيم المشتقة فخ آخر. العدّات، القوائم المصفاة، المجاميع، canSubmit، وisEmpty عادةً يجب أن تُحسب من المدخلات. إذا أصبح الأداء مشكلة حقيقية، قم بالتحسين لاحقًا، لكن لا تبدأ بتخزين النتيجة.
مخزن عملاق واحد لكل شيء (auth، مودالات، تنبيهات، فلاتر، مسودات، علم التمهيد) يتحول إلى مطرح قمامة. قسّم بحسب حدود الميزة. إذا كانت الحالة تُستخدم من شاشة واحدة فقط، احتفظ بها محلية.
Context ممتاز للقيم الثابتة (الثيم، معرف المستخدم الحالي، اللغة). للقيم سريعة التغير، قد يسبب إعادة عرض واسعة. استخدم Context للتركيب، وحالة المكونات (أو مخزن صغير) للقيم المتغيرة بسرعة.
أخيرًا، تجنب التسمية غير المتسقة. مفاتيح استعلام وحقول مخزن متقاربة تُنشئ تكرارًا دقيقًا. اختر معيارًا بسيطًا والتزم به.
عندما تشعر بالرغبة في إضافة "متغير حالة إضافي"، قم بسرعة بفحص الملكية.
أولًا، هل يمكنك الإشارة إلى مكان واحد يحدث فيه الجلب والتخزين المؤقت (أداة استعلام واحدة، مجموعة مفاتيح استعلام واحدة)؟ إذا كانت نفس البيانات تُسترجع في مكونات متعددة وتُنسخ أيضًا إلى مخزن، فأنت بالفعل تدفع الفائدة.
ثانيًا، هل هذه القيمة مطلوبة داخل شاشة واحدة فقط (مثل "هل لوحة الفلتر مفتوحة")؟ إذا كذلك، فلا يجب أن تكون عامة.
ثالثًا، هل يمكنك تخزين معرف بدلًا من تكرار كائن؟ خزّن selectedUserId واقرأ المستخدم من الكاش أو القائمة.
رابعًا، هل هي مشتقة؟ إذا يمكنك حسابها من الحالة الموجودة، لا تخزنها.
أخيرًا، اختبر التتبع لمدة دقيقة. إذا لم يستطع زميلك الإجابة "من أين تأتي هذه القيمة؟" (prop، حالة محلية، كاش الخادم، URL، مخزن) في أقل من دقيقة، أصلح الملكية قبل إضافة المزيد.
ابدأ بتصنيف كل قطعة حالة إلى خادم، عميل (واجهة)، أو مشتق.
isValid).بعد التصنيف، تأكد أن لكل عنصر مالك واضح واحد (ذاكرة التخزين المؤقت للاستعلامات، حالة محلية للمكون، الـ URL، أو مخزن صغير).
استخدم هذا الاختبار السريع: «هل يمكنني تحديث الصفحة وإعادة بناء هذا من الخادم؟»
مثال: قائمة المشاريع هي حالة خادم؛ معرف الصف المحدد هو حالة عميل.
لأنها تخلق مصدرين للحقائق.
إذا استرجعت users ثم نسختها إلى useState أو مخزن عام، فستحتاج لمزامنتها خلال:
القاعدة الافتراضية: وأنشئ حالة محلية فقط لمخاوف الواجهة أو المسودات.
خزن القيم المشتقة فقط عندما لا تستطيع بالفعل حسابها بتكلفة بسيطة.
عادةً احسبها من المدخلات الموجودة:
visibleUsers = users.filter(...)total = items.reduce(...)canSubmit = isValid && !isSavingإذا أصبح الأداء مشكلة حقيقية (مقاسة)، فالأفضل استخدام أو هياكل بيانات محسّنة قبل إدخال حالة مخزنة إضافية قد تصبح قديمة.
الخطة الافتراضية: استخدم أداة حالة-الخادم (مثل TanStack Query) حتى تطلب المكونات البيانات فقط وتتعامل مع حالات التحميل/الخطأ بدون أن تهتم بعدد استدعاءات الجلب تحت الغطاء.
أساسيات عملية:
تجنب نشر في أماكن متعددة "فقط للسلامة".
اجعلها محلية حتى تستطيع تسمية الحاجة الحقيقية للمشاركة.
قاعدة الترويج:
هذا يمنع تحول المخزن العام إلى مكان لتجميع أعلام واجهة عشوائية.
خزن المعرّفات والأعلام الصغيرة، لا الكائنات الكاملة من الخادم.
مثال:
selectedUserIdselectedUser (نسخة من الكائن)ثم اعرض التفاصيل بالبحث عن المستخدم عبر القائمة المخبأة/الكاش. هذا يجعل عمليات إعادة الاسترجاع الخلفية والتحديثات تعمل بشكل صحيح بدون شيفرة مزامنة إضافية.
عامل النموذج كـ مسودة (حالة عميل) حتى تضغط حفظ.
نمط عملي:
هذا يتجنب تعديل بيانات الخادم "في المكان" ومواجهة تعارضات إعادة الاسترجاع.
علامات الإنذار الشائعة:
needsRefresh, didInit, isSaving تتراكم.الحل عادة ليس مكتبة جديدة بل حذف النسخ وتحديد مالك واحد لكل قيمة.
الشاشات المولّدة قد تنزلق إلى أنماط مختلطة بسرعة. وسيلة وقائية بسيطة هي توحيد الملكية:
إذا كنت تستخدم Koder.ai (koder.ai)، استخدم Planning Mode للاتفاق على حدود الحالة قبل توليد شاشات جديدة، واعتمد على اللقطات/الرجوع لتجربة التغييرات بأمان.
اختر ميزة واحدة (مثل تعديل الملف الشخصي)، طبّق القواعد من البداية، واجعلها المثال الذي ينسخه الجميع.
useMemorefetch()