Search⌘ K
AI Features

اختبار الخطافات والمنطق غير المتزامن

تعلم كيفية تصميم واختبار عمليات إرسال نموذج غير المتزامنة كنظم عرض معاملات باستخدام useActionState و useOptimistic، مما يضمن أن تظل واجهة المستخدم المتفائلة والتراجع وإعادة المحاولة صحيحة سلوكيًا في ظل التزامن.

في العروض التوضيحية المصغرة، يبدو إرسال نموذج بسيطًا: انقر على "إرسال"، انتظر استجابة خادم، ثم اعرض رسالة. لكن أنظمة الإنتاج نادرًا ما تعمل بهذه الطريقة. من المتوقع أن تكون واجهات المستخدم الحديثة فورية. عندما يُرسل المستخدم نموذج، أو يُجري طلبًا، أو يحفظ ملفًا شخصيًا، أو ينشر تعليقًا، فإنه يتوقع ردًا فوريًا. غالبًا ما يتم تحديث واجهة المستخدم بشكل متفائل قبل استجابة خادم . إذا رفض خادم طلب لاحقًا، يجب أن تتراجع واجهة المستخدم بسلاسة دون فقدان السياق. إذا فشل طلب بسبب عدم استقرار الشبكة، يجب أن ندعم إعادة المحاولة دون تكرار العملية.

وهذا يطرح مشكلة معمارية حقيقية. لدينا الآن حقائق متعددة متداخلة:

  • نية المستخدم

  • حالة واجهة المستخدم المتفائلة

  • الحالة المؤكدة من قبل الخادم

  • إمكانية التراجع

  • الحاجة إلى إعادة المحاولة بأمان

في نموذج العرض المتزامن لـ React 19، تُجدول التحديثات وتُنسق. قد تتداخل العمليات غير المتزامنة. وقد تتداخل الحالات المعلقة. وبدون بنية محددة، ينتشر منطق الإرسال عبر عدة عناصر.useState المكالمات، مما يؤدي إلى علامات معلقة غير متناسقة، أو مكالمات شبكة مكررة، أو واجهة مستخدم عالقة في حالات جزئية.

يصبح الاختبار هشًا بنفس القدر. غالبًا ما تختبر الفرق انتقالات الحالة الداخلية، وتعتمد على مهلات زمنية مصطنعة، أو تتحقق من تفاصيل التنفيذ بدلًا من النتائج التي يراها المستخدم. عندما تتغير المنطق المتفائل أو يُعيد التزامن تشكيل التوقيت، تفشل الاختبارات، ليس لأن السلوك خاطئ، بل لأن الافتراضات كانت مرتبطة بالآليات.

المشكلة الحقيقية التي نعمل على حلها هي كالتالي:

كيف نصمم ونختبر عمليات إرسال نموذج غير المتزامنة بحيث تظل التحديثات المتفائلة والتراجع وإعادة المحاولة قابلة للتنبؤ، حتى في ظل الجدولة المتزامنة؟

يقدم React 19 عناصر التنسيق الأساسية التي تُضفي الطابع الرسمي على هذا النمط.useActionState يُصمم نموذج دورة حياة التقديم كآلة حالة مُتحكم بها.useOptimistic يُمكّننا هذا من إجراء تحديثات فورية لواجهة المستخدم، والتي يمكن مواءمتها لاحقًا مع البيانات خادم . وبذلك، يسمح لنا بالتعامل مع عمليات الإرسال كمعاملات، بدلاً من كونها آثارًا جانبية غير متزامنة.

Transactional forms commit optimistically, reconcile deterministically, and expose only committed phases as the testing contract
Transactional forms commit optimistically, reconcile deterministically, and expose only committed phases as the testing contract

تخيل مخططًا زمنيًا أفقيًا مُعنونًا كالتالي: إرسال ← تأكيد مبدئي ← استجابة الخادم ← التوفيق ← (حلقة إعادة محاولة اختيارية). مباشرةً بعد الإرسال، تُؤكد واجهة المستخدم حالةً مُتفائلة. أثناء انتظار الاستجابة، تُشير الواجهة إلى أن العمل جارٍ. عند استجابة خادم ، يُؤكد التوفيق التغيير المتفائل أو يُلغيه ويُظهر خطأً. إذا أعاد المستخدم المحاولة، تبدأ الدورة من جديد دون تكرار الحالة أو فقدان السياق. قد يُدخل مُجدول React عمليات عرض مُتداخلة خلال هذا التدفق، لكن المراحل المرئية تظل مُرتبة ومُتسقة.

تقديم المعاملة كدورة عرض من أربع مراحل

يمكن نمذجة عملية تقديم المعاملات كنظام عرض من أربع مراحل.

أولاً، هناك نية. ينقر المستخدم على "إرسال"، ويبدأ هذا الحدث عملية انتقال مُتحكَّم بها في واجهة المستخدم. ثانياً، قد يحدث تأكيد مُتفائل. باستخدامuseOptimistic في هذه المرحلة، تعكس واجهة المستخدم مؤقتًا النتيجة المتوقعة، كما لو أن خادم قد أكد نجاح العملية. ثالثًا، يقوم خادم بحل المشكلة. إما أن يتم تنفيذ الإجراء غير المتزامن أو رفضه. رابعًا، تتم عملية التوفيق. إذا أكد خادم نجاح العملية، تصبح الحالة المتفائلة دائمة. أما إذا فشلت، فتعود واجهة المستخدم إلى آخر حالة مستقرة مع عرض رسالة خطأ واضحة. يمكن إعادة محاولة العملية دون التأثير على الحالة.

الuseActionState يُركز هذا النهج دورة حياة العملية. فبدلاً من توزيع علامات الانتظار والنجاح والخطأ على متغيرات حالة غير مترابطة، يصبح الإرسال انتقال حالة حتميًا مدفوعًا بإجراء غير متزامن. وهذا يجعل سلوك التراجع وإعادة المحاولة واضحًا وأسهل فهمًا.

يبقى مبدأ الاختبار كما هو: استخدام الخطافات الداخلية وانتقالات المُخفِّضات ليسا جزءًا من العقد. العقد هو واجهة المستخدم القابلة للملاحظة والملتزمة في كل مرحلة.

  • ما الذي يظهر مباشرة بعد الإرسال؟ ...