تقسيم المكونات الدقيقة وقضايا ملكية الدولة
بعد تقطيع الحالة إلى حقائق محلية متعددة، يصبح التسلسل حدثًا احتماليًا
أعراض هذا الخطأ عبر الإنترنت تشبه إلى حد كبير الأعراض “العرضية”، ولكنها ليست عشوائية.
في نفس الصفحة، ستوجد حالة العمل بأشكال مختلفة في مكونات مختلفة: معلمات URL، وحالة المكون الأصلي، والحالة المحلية للمكون الفرعي، وذاكرة التخزين المؤقت التي يتم إرجاعها بواسطة الطلب، وحتى القيم المشتقة المحسوبة بواسطة محدد معين. كلما تم تقسيم المكونات بشكل أدق، كلما أصبحت هذه “الحقائق الجزئية” أكثر. طالما أن “من يمكنه الكتابة، ومن يعرف، ومن المسؤول عن التوقيت” لا يتم دمجه في قاعدة واحدة أولاً، فإن السبب الجذري لحالة الخطأ عبر الإنترنت سيتغير من “كتابة جزء معين من التعليمات البرمجية بشكل خاطئ” إلى “تمت كتابة أجزاء متعددة من التعليمات البرمجية بشكل صحيح، ولكن ترتيب الكتابة غير مستقر”.
أصعب شيء في هذا النوع من المشاكل هو استكشاف الأخطاء وإصلاحها، وليس إصلاحها. لأنه يبدو أن كل مكون معقول جدًا: فهم يحافظون على جزء صغير من حالتهم، والتخزين المؤقت، والتحميل. ولكن عند الدمج، لا يحتوي النظام على مالك حالة فريد، ويكون الأداء النهائي هو: ما عليك سوى التحديث وتبديل علامات التبويب والمحاولة مرة أخرى. إذا كنت تريد استخدام السجلات لربط الروابط، فستجد أن نفس الحقل يأتي من الدعائم وذاكرة التخزين المؤقت المحلية وحزم طلب الإرجاع. من يغطي من يعتمد كليًا على إيقاع العرض وتأخير الطلب.
الحكم في هذه المقالة بسيط:
إذا لم يتقارب تقسيم المكونات أولاً حول “من يكتب الحالة، ومن يعرف التفاصيل، ومن المسؤول عن التوقيت”، فسيتم تقسيم نفس الحالة إلى حقائق جزئية متعددة، وسيصبح تسلسل التحديث حدثًا احتماليًا. وأخيرًا، سيتم استبدال فوائد إعادة الاستخدام بحالات خطأ عرضية، وستحدث تكلفة العرض المتكرر واستكشاف الأخطاء وإصلاحها.
سأستخدم أدناه استكشاف الأخطاء وإصلاحها لحالة الخطأ عبر الإنترنت النموذجية جدًا لشرح كيفية دمجها خطوة بخطوة.
المشهد: حالة خطأ “عرضية”.
الصفحة عبارة عن قائمة + شريط التصفية العلوي.
- يوجد استعلام:
?tab=all&sort=latest&city=shعلى عنوان URL - يتم تقسيم شريط التصفية العلوي إلى عدة مكونات صغيرة: علامة التبويب، والفرز، والمدينة المنسدلة
- يقوم مكون القائمة بإنشاء ذاكرة تخزين مؤقت لـ “نتيجة الطلب الأخير” لتجنب الوميض عند تبديل عوامل التصفية.
تعليقات المستخدم هي: عند التبديل السريع بين علامات التبويب والفرز، ستعرض القائمة أحيانًا “عناصر التصفية للفرز الجديد”، لكن بيانات القائمة تظل “نتائج الفرز القديم”. النقر فوق نفس الفرز مرة أخرى يعمل بشكل جيد.
للوهلة الأولى، يبدو أن الواجهة غير متسقة، لكن التقاط الحزمة يوضح أن الواجهة لا تُرجع أي مشكلة، كما أن صدى sort في النص الذي تم إرجاعه صحيح أيضًا. بمعنى آخر، الخادم على حق، والخطأ هو “الحالة المعروضة” في الواجهة الأمامية.
سوء التقدير الأول: ظن أنه شرط سباق الطلب
سوف يجعلك الحدس تشك: طلب A بطيء، وطلب B سريع، ويعود B أولاً ويعرض النتيجة الصحيحة، ثم يعود A ويستبدل النتيجة القديمة.
هذا النوع من حالات السباق شائع بالفعل، لذا نضيف أولاً معرف الطلب ونتجاهل حزمة الإرجاع: يتم قبول آخر طلب تم إرساله فقط.
وبعد الاتصال بالإنترنت، خفت المشكلة قليلاً، لكنها لم تختف. اشرح أن “تغطية حزمة الإرجاع” ليست القناة الوحيدة.
قيمة هذه الخطوة هي: أنها تقطع أولاً جزءًا من مساحة المشكلة التي تبدو كبيرة. أصبح من المؤكد الآن أن بعض الحالات الخاطئة على الأقل لا تنتج عن ترتيب الشبكة.
سوء التقدير الثاني: اعتقدت أنه خطأ في منطق ذاكرة التخزين المؤقت.
ثم دعونا نلقي نظرة على ذاكرة التخزين المؤقت لمكون القائمة.
استراتيجية التخزين المؤقت هي:
- قم بتمرير
filtersمن الدعائم - يستخدم مكون القائمة
useRefداخليًا لحفظlastGoodData - قم بتشغيل الطلب في حالة تغير
filters - استمر في عرض
lastGoodDataأثناء الطلب، ثم استبدله عند عودة البيانات الجديدة
لا حرج في هذا المنطق في “تقليل الوميض”، لكنه يدفن فرضية: يجب أن يكون filters مصدرًا ثابتًا ووحيدًا للحقيقة. وإلا فمن السهل أن يظهر: لقد تغير filters، لكن القائمة لا تزال تستخدم lastGoodData القديم، وعلى السطح سيُعتقد أنه مجرد غطاء أثناء التحميل.
اعتقدت أن السبب في ذلك هو أن مرجع كائن filters كان غير مستقر، مما تسبب في الخلط بين توقيت تشغيل التأثير. تم التغيير إلى مفتاح التسلسل الصريح: filtersKey = tab + sort + city.
لا يوجد علاج حتى الآن.
السبب الجذري الحقيقي: تمزيق ملكية الدولة
بعد كتابة كافة السجلات أخيرا، أصبحت المشكلة واضحة:
- يهتم مكون Tab فقط بـ
tab، وسوف: - عند النقر عليه، سيتم تمييز
setLocalTab(nextTab)على الفور - ثم يقوم
onChange(nextTab)بإعلام المكون الأصلي - انتقل إلى
setFilters({ ... })بعد استلام المكون الأصلي - مكون الفرز له نفس النمط أيضًا
- من أجل دعم “التحديث القابل للاستئناف”، سيقوم المكون الأصلي بما يلي:
- قم أولاً باستخراج عوامل التصفية الأولية من تحليل عنوان URL
- ثم اكتبه للدولة
- تتلقى مكونات القائمة أيضًا:
- دعائم
filtersللمكون الأصلي - وواحد
getCachedResult(filtersKey)من وحدة ذاكرة التخزين المؤقت
بمعنى آخر، تمتلك الدولة ثلاث مجموعات من المصادر على الأقل:
- الحالة المحلية للمكون الفرعي: تستخدم للتفاعل الفوري مع ردود الفعل
- حالة المكون الأصلي: كمرشحات على مستوى الصفحة
- وحدة ذاكرة التخزين المؤقت: تستخدم كواجهة خلفية لعرض البيانات
لا يوجد عقد “أمر كتابة” صارم بينهما.
عادةً ما يكون الرابط الذي تحدث فيه المشكلة كما يلي:
- فرز نقاط المستخدم
- يقوم مكون الفرز بتحديث حالته المحلية على الفور، وتعرض واجهة المستخدم “تم تحديد فرز جديد”
- لم يكن لدى
filtersالخاص بالمكون الأصلي الوقت الكافي للتحديث (أو تم تحديثه ولكن لن يتم تمريره حتى الإطار التالي) - سيقوم مكون القائمة بإعادة حساب
filtersKeyفي هذا الوقت - ولكنها لا تحسب المرشحات الجديدة للمكون الأصلي
- بدلاً من ذلك، يختلط المسار المشتق بالقيمة المحلية للفرز (على سبيل المثال من خلال السياق أو المحدد)
- تم تغيير
filtersKey، لذلك انتقلت القائمة إلى وحدة ذاكرة التخزين المؤقت وجلبت نتيجة قديمة “تبدو مطابقة” - عندما يعود الطلب، بسبب سياسة تجاهل معرف الطلب، طالما أنها ليست المرة الأخيرة، سيتم تجاهله
- تحتوي واجهة المستخدم النهائية على مزيج غريب: شريط التصفية هو القيمة الجديدة، وبيانات القائمة تأتي من ذاكرة التخزين المؤقت القديمة
هذا يعني أن “أجزاء متعددة من التعليمات البرمجية مكتوبة بشكل صحيح، ولكن الترتيب غير مستقر.”
يؤدي تقسيم المكون إلى قطع حقوق كتابة الحالة إلى أجزاء: يكتب المكون الفرعي نسخة واحدة أولاً للحصول على تعليقات فورية للتفاعل، ويكتب المكون الأصلي نسخة أخرى للتشغيل، وتكتب ذاكرة التخزين المؤقت نسخة أخرى من أجل الخبرة. لا حرج في أي شيء، لكن النظام يفتقر إلى ملكية دولة موحدة.
كيف تتوقف عن الحديث: قرر الملكية أولاً، ثم تحدث عن إعادة الاستخدام
الحل لهذا النوع من المشاكل هو كتابة حقوق الكتابة للدولة وقواعد الاشتقاق واستراتيجيات التستر في عقد قابل للتنفيذ.
انتهى بي الأمر بالاستقرار على ثلاث قواعد.
القاعدة 1: مرشحات الصفحة لها مصدر واحد فقط قابل للكتابة
لم تعد المكونات الفرعية تحتفظ بحالة عوامل التصفية المحلية الخاصة بها.
يتم توفير ردود فعل تفاعلية فورية من خلال حالة المكون الأصلي، ويكون المكون الفرعي مسؤولاً فقط عن إرسال الأحداث وليس تخزين القيم. هذا هو:
- التجميع الفرعي:
onSelect(next) - المكون الأصلي:
setFilters(reduce(prev, action)) - عرض المكونات الفرعية:
value={filters.sort}للقراءة فقط
تكلفة ذلك هي: سيصبح المكون الفرعي “غبيًا” وسيلزم تمرير المزيد من الدعائم عند إعادة استخدامه. لكن ما يتبادله هو مسار كتابة معين.
القاعدة 2: القيم المشتقة يجب أن تشير بوضوح إلى المصدر
يُسمح فقط بإنشاء جميع المفاتيح المستخدمة للطلبات والتخزين المؤقت من filters.
يحظر إنشاء مجموعة أخرى من المفاتيح مباشرة من السياق أو المحدد أو عنوان URL.
قد يبدو هذا أمرًا غامضًا، ولكنه يهدف إلى تجنب “الحقول التي تحمل نفس الاسم والتي تأتي من مصادر مختلفة”. بمجرد السماح لـ sort بأن يأتي من كل من الحالة المحلية والحالة الأم، ستواجه اليوم “مجموعة واحدة من واجهة المستخدم ومجموعة أخرى من البيانات”.
القاعدة 3: تعرض ذاكرة التخزين المؤقت التفاصيل فقط ولا تشارك في الحكم على الحالة.
توفر وحدة ذاكرة التخزين المؤقت إمكانية واحدة فقط: getLastGoodData(filtersKey).
لا يمكنه تحديد ما هو مفتاح التصفية الحالي، ناهيك عن استخدام بيانات المفتاح القديم باعتبارها “النتيجة الحالية”.
الخطوات المحددة هي:
- حالة طلب القائمة واضحة: يتم تمرير
currentFiltersKeyمن المكون الأصلي - مسموح عند العرض:
data = loading ? cache[currentFiltersKey] ?? null : result- لكن التخزين المؤقت لا يؤثر أبدًا على عوامل التصفية
يؤدي هذا إلى خفض مستوى ذاكرة التخزين المؤقت من “المشاركة في حالة النظام” إلى “عرض النتيجة النهائية فقط”. ستضحي بالقليل من الخبرة، على سبيل المثال، ستكون بعض المفاتيح فارغة لفترة من الوقت، لكنها ستعيد اليقين.
مثال مضاد: “ترقية جميع الحالات إلى المكون الأصلي” ستفشل أيضًا
بعد سماع هذا، سيقول البعض: فقط ارفعوا جميع الدول إلى المستوى الأعلى.
النسخة التي رأيتها تفشل هي: الطبقة العليا تصبح المصدر الوحيد للحقيقة، ولكنها تحمل أيضًا:
- مزامنة URL
- الثبات المحلي
- طلب اختناق
- ضرب ذاكرة التخزين المؤقت
- الحالة التفاعلية لواجهة المستخدم (التمرير والتركيز واللوحة مفتوحة)
ونتيجة لذلك، يصبح المخفض ذو المستوى الأعلى جوهر الأعمال، وأي تفاعل صغير يجب أن يمر عبر مجموعة من المنطق، وترتفع تكلفة التعديل بشكل كبير. في النهاية، بدأ الجميع في تجاوزه وإضافة الحالة المحلية سرًا إلى المكونات الفرعية، وعاد النظام إلى “الحقائق المحلية المتعددة”.
لذا فإن المفتاح هو “تحديد الولايات التي تتطلب الملكية الفردية”.
أحكم عليه عمومًا بجملة واحدة: طالما أنه يؤثر على الطلبات أو مفاتيح التخزين المؤقت أو الاتساق بين المكونات، فيجب أن يكون مملوكًا بشكل فريد. يمكن ترجمة عابري واجهة المستخدم النقية.
الحدود القابلة للتطبيق: لن تتسبب جميع انقسامات المكونات في حدوث أخطاء
لا يتعلق هذا النقد بتقسيم المكونات في حد ذاته.
كان المقصود من التقسيم في الأصل التحكم في التعقيد، ولكن له تكلفة ضمنية: إذ يجب تصميم “حدود كتابة الدولة” بشكل إضافي.
عندما تستوفي الصفحة أيًا من الشروط التالية، ستظهر هذه المصيدة بسهولة:
- لديه عنوان URL/الاسترداد المستمر
- هناك طلبات أو إلغاءات متزامنة
- هناك عرض مخبأ
- هل لديك مرشحات مشتركة عبر المكونات
من ناحية أخرى، إذا كانت الصفحة بسيطة للغاية، فلن تؤثر الحالة على الطلب، ولا يوجد تخزين مؤقت واسترداد، فلن تصبح الحالة المحلية كارثة.
ملخص
لن يؤدي تقسيم المكونات إلى قطع صغيرة إلى تحقيق فوائد إعادة الاستخدام تلقائيًا؛ سيخلق أولاً قضايا ملكية الدولة.
إذا لم تجب أولاً “من يستطيع الكتابة، من يعرف التفاصيل، ومن المسؤول عن التوقيت”، سيجيب النظام من تلقاء نفسه: من يقدم أولاً ستكون له الكلمة الأخيرة، ومن يعود متأخراً سيغطيه.
عندما تحدث حالات خطأ “عرضية” على الخط، ستجد أن ما هو مكلف حقًا هو استعادة الحالة من حقائق جزئية متعددة إلى رابط كتابة معين. \
What to read next
Want more posts about Uncategorized?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home