الانتقال إلى المحتوى

تدفقات النماذج بآلة الحالة

نموذج من 12 خطوة. تحقق شرطي. وزر الرجوع يجب أن يعمل.

نمذجة مسار عام لاستقبال المستثمرين باستخدام XState وأحد عشر شرط تحقق وسياق نموذج محفوظ واختبارات على مستوى المتصفح.

قراءة في 6 دقائقبقلم

كانت الانتقالات جزءًا من المنتج

كان عداد الخطوات كافيًا في البداية فقط. بحلول الخطوة الخامسة، كان التدفق يتتبع الموضع والبيانات المتراكمة وأخطاء التحقق والتحميل والخطوات المكتملة والمتطلبات الشرطية. وبحلول الخطوة العاشرة، بلغ مكون واحد نحو 800 سطر، وكانت إجابة سؤال «هل يستطيع هذا المستخدم المتابعة؟» موزعة على ستة مواضع.

كان عرض اثنتي عشرة شاشة مباشرًا، لكن إبقاء الانتقالات إلى الأمام والخلف وغير المتزامنة وانتقالات الإرسال متسقة من دون فقد المدخلات السابقة لم يكن كذلك.

يمكن للحالة المنظمة أن تسمح بحالات مستحيلة

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

جعل XState v5 مع @xstate/react ذلك الرسم صريحًا. لا يزال ممكنًا إرسال حدث، لكن الانتقال غير المعرّف أو المرفوض بشرط لا يحرك الآلة.

اجعل عقد سير العمل صريحًا

استخدمت الآلة سبعة أحداث: NEXT وPREVIOUS وGO_TO_STEP وUPDATE_FORM_DATA وSET_CREATED_BUYER_ID وSUBMIT وRESET. واحتوى السياق currentStep وformData وerrors وisLoading وinvestorId وcreatedBuyerId.

CONTACT_PREFERENCES: {
  on: {
    NEXT: {
      target: 'TARGET_MARKETS',
      guard: 'CAN_PROGRESS_FROM_CONTACT_PREFERENCES'
    },
    PREVIOUS: 'INTRO',
    UPDATE_FORM_DATA: { actions: 'UPDATE_FORM_DATA' }
  }
}

كان NEXT محميًا بشرط. وظل PREVIOUS متاحًا خارج الإرسال، كي يصحح المستخدم البيانات السابقة من دون تصفير سياق النموذج. ودمج UPDATE_FORM_DATA الحقول المتغيرة فقط في السياق المتراكم.

ضع التحقق عند حد الانتقال

اتبع أحد عشر شرطًا نمط التسمية CAN_PROGRESS_FROM_[STEP_NAME]. اشترط مخطط Zod وجود هاتف أو بريد إلكتروني على الأقل، ولم يتحقق من صحة رقم هاتف أميركي إلا عند إدخاله. وتطلّب كل من الأسواق المستهدفة وأنواع العقارات والاستراتيجيات اختيارًا؛ بينما تطلّبت شاشات النطاق حدًا أدنى أو أقصى على الأقل.

const canGoNext = state.can({ type: "NEXT" })

CAN_PROGRESS_FROM_CONTACT_CARD: ({ context }) =>
  Boolean(context.createdBuyerId)

كان شرط Contact Card يعيد true دائمًا في الأصل. ربطه إصلاح لاحق بـ createdBuyerId كي لا تغادر الآلة تلك الحالة قبل نجاح إنشاء المشتري في النظام الخلفي. قاد عقد state.can({ type: "NEXT" }) نفسه حالة الزر والانتقال.

منع التحقق غير المتزامن من التسابق مع النموذج

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

حدث إنشاء المشتري عند حد Contact Card. خزّن SET_CREATED_BUYER_ID معرّف النظام الخلفي المؤكد في سياق الآلة؛ واستخدمت شاشات التفضيلات اللاحقة ذلك المعرّف لتحديثات API.

أبقِ مكونات React غير مدركة لرسم المسارات

غلف مزود useMachine وكشف currentStep وformData وcanGoNext وisSubmitting وhandleNext وhandlePrevious وhandleUpdateFormData وإجراء معرّف المشتري. عرضت مكونات الخطوات حقولها وأرسلت التحديثات من دون معرفة المسار السابق أو اللاحق.

تمتلك الآلة التنقل. يعرض كل مكون React حقوله، ويحدث السياق، ويرسل الأحداث. لن أستخدم XState لنموذج من ثلاث خطوات؛ لكن اثنتي عشرة شاشة والتحقق الشرطي والانتقالات المعتمدة على النظام الخلفي جعلت الرسم الصريح يستحق الإعداد الإضافي هنا.

اختبر القواعد بمعزل عن الشاشات

أضاف التنفيذ 538 سطرًا من تغطية الوحدات للمخططات والانتقالات المباشرة. أرسلت تلك الاختبارات الأحداث إلى الآلة وتحققت من الحالة من دون تركيب مكونات React. وغطت 359 سطرًا أخرى من Playwright التدفق الكامل وقواعد الهاتف والبريد الإلكتروني وإنشاء المشتري في النظام الخلفي وحفظ البيانات عند الرجوع.

  • 12 خطوة
  • سبعة أحداث
  • 11 شرطًا مسمى للتقدم
  • ملف آلة واحد يتحكم في التنقل
  • اختبارات المخطط والآلة منفصلة عن اختبارات التفاعل في المتصفح