MAISON CODE .
/ UX · B2B · React · Performance

ترتيب المصفوفة: شبكات B2B الهندسية عالية الأداء

نظرة عميقة على بناء شبكات ترتيب مصفوفة B2B عالية الأداء لآلاف المتغيرات. إتقان المحاكاة الافتراضية للتفاعل وإدارة الحالة وتجميع واجهة برمجة التطبيقات.

AB
Alex B.
ترتيب المصفوفة: شبكات B2B الهندسية عالية الأداء

في عالم التجارة الإلكترونية B2C سريع الخطى، تكون رحلة المستخدم خطية: تصفح، حدد، أضف إلى سلة التسوق. لكن B2B هو وحش مختلف تمامًا. لا يرغب مشتري الجملة لمتاجر الأزياء بالتجزئة في النقر فوق “إضافة إلى سلة التسوق” خمسين مرة للحصول على خمسين مقاسًا مختلفًا للقميص. يريدون الكفاءة. يريدون السرعة. إنهم يريدون مصفوفة.

في Maison Code Paris، رأينا بوابات B2B تنهار تحت ثقلها. لقد رأينا حلول “المؤسسات” التي تستغرق 4 ثوانٍ لعرض صفحة المنتج لأنها تعرض بسذاجة 2000 مُدخل لنوع برغي واحد. هذا غير مقبول. في اقتصاد الجملة، لا يؤدي الاحتكاك إلى إزعاج المستخدم فحسب؛ إنه يدمر حلقة الإيرادات المتكررة.

يعد هذا الدليل بمثابة بحث هندسي عميق في بناء “برنامج Excel للتجارة الإلكترونية”: شبكة ترتيب المصفوفة.

لماذا تتحدث Maison Code عن هذا

في Maison Code Paris، نعمل كضمير معمari لعملائنا. غالبًا ما نرث حزمًا “حديثة” تم بناؤها دون فهم أساسي للحجم.

نناقش هذا الموضوع لأنه يمثل نقطة تحول حاسمة في النضج الهندسي. التنفيذ الصحيح يميز MVP الهش عن منصة مؤسسية مرنة يمكنها التعامل مع حركة مرور الجمعة السوداء.

لماذا تناقش Maison Code ترتيب المصفوفات

نحن نبني منصات B2B واسعة النطاق للمنازل الفاخرة والصناعية العملاقة. غالبًا ما يكون الفرق بين الأداة التي تبدو وكأنها SaaS حديثة والأداة التي تبدو وكأنها نظام تخطيط موارد المؤسسات (ERP) القديم هو Matrix Grid. عندما يتمكن المشتري من إدخال كميات لـ 500 متغير في ثوانٍ، باستخدام التنقل عبر لوحة المفاتيح، دون أي تأخير، فإنه يشعر بالاحترافية. يشعرون بالاحترام.

نناقش هذا الأمر لأنه يقع عند تقاطع القيود التقنية الثقيلة (حدود DOM وحدود واجهة برمجة التطبيقات) وتجربة المستخدم ذات القيمة العالية. ويتطلب حلها أكثر من مجرد مكتبة واجهة المستخدم؛ فهو يتطلب فهمًا أساسيًا لكيفية عرض المتصفح وكيفية تدفق الحالة عبر التطبيق.

مشكلة قابلية التوسع: عرض O(n).

فكر في منتج B2B بسيط: قميص.

  • الألوان: 10
  • المقاسات: 8
  • إجمالي المتغيرات: 80 مدخلاً.

يتيح لك React تقديم 80 مُدخلًا دون بذل أي جهد.

الآن فكر في الترباس الصناعي.

  • الأطوال: 50
  • ملاعب الموضوع: 20
  • المواد: 10
  • إجمالي المتغيرات: 10000 مُدخل.

إذا حاولت عرض 10000 عنصر <input> في DOM في وقت واحد، فسيتجمد تطبيقك. عنق الزجاجة ليس تنفيذ JavaScript؛ إنها مرحلتي التخطيط والطلاء لمحرك المتصفح. في كل مرة يكتب فيها المستخدم حرفًا في مربع واحد، إذا كانت إدارة الحالة لديك ساذجة، فقد تحاول React التوفيق بين الشجرة بأكملها.

تكلفة إعادة العرض

إذا كنت تستخدم كائن useState واحد للنموذج بأكمله:

const [formState, setFormState] = useState({});

تؤدي كل ضغطة على المفتاح إلى إعادة عرض المكون الأصلي، والذي يؤدي بعد ذلك إلى إعادة عرض 10000 طفل. حتى مع React.memo، فإن اختلاف الخاصية وحدها لـ 10000 مكون سيؤدي إلى تأخر ملحوظ في الإدخال. في الأداة الاحترافية، يكون تأخر الإدخال أمرًا قاتلاً.

المرحلة الأولى: المحاكاة الافتراضية (حل DOM)

الخطوة الأولى نحو الأداء هي الاعتراف بأن المستخدم لا يمكنه النظر إلى 10000 مدخلات في وقت واحد. تعرض الشاشة النموذجية ربما 50 صفًا من البيانات.

المحاكاة الافتراضية (أو “النافذة”) هي تقنية عرض فقط عقد DOM المرئية حاليًا في إطار العرض. أثناء قيام المستخدم بالتمرير، نقوم بتدمير العقد التي تترك الجزء العلوي من الشاشة وننشئ عقدًا جديدة تدخل الجزء السفلي. يعتقد المتصفح أنه يقوم بتمرير عنصر بحجم 5000 بكسل، لكن DOM يحتوي فقط على 50 عنصرًا div.

نوصي بـ TanStack Virtual (بدون رأس) أو react-window لهذا التنفيذ.

نمط التنفيذ

إليك كيفية بناء شبكة افتراضية لمصفوفة B2B. نحن نتعامل مع الشبكة كنظام إحداثي (صف، عمود).

استيراد { useVirtualizer } من '@tanstack/react-virtual'؛
استيراد { useRef } من 'react'؛

// "المصدر الوحيد للحقيقة" لبيانات المصفوفة
// من الأفضل عادةً أن تكون مسطحة أو منظمة كـ Map<VariantID,quantity>
اكتب MatrixData = Record<string, number>;

تصدير const VirtualMatrix = ({ الصفوف والأعمدة والبيانات }: MatrixProps) => {
  constparentRef = useRef(null);

  constrowVirtualizer = useVirtualizer({
    العد: صفوف.طول،
    getScrollElement: () =>parentRef.current،
    تقدير الحجم: () => 50، // 50 بكسل ارتفاع الصف
    OVERSCAN: 5، // عرض 5 صفوف إضافية للتمرير السلس
  });

العودة (
    <div
      المرجع = {parentRef}
      النمط={{
        الارتفاع: `600 بكسل`،
        الفائض: "تلقائي"،
      }}
    >
      <div
        النمط={{
          الارتفاع: `${rowVirtualizer.getTotalSize()}px`,
          العرض: '100%'،
          الموقف: "نسبي" ،
        }}
      >
        {rowVirtualizer.getVirtualItems().map((virtualRow) => {
          constrowData =rows[virtualRow.index];
          العودة (
            <div
              مفتاح={virtualRow.key}
              النمط={{
                الموقف: "مطلق"،
                أعلى: 0،
                اليسار: 0،
                العرض: '100%'،
                الارتفاع: `${virtualRow.size}px`،
                تحويل: `translateY(${virtualRow.start}px)`,
                عرض: "فليكس"،
              }}
            >
              {/* عرض الأعمدة (الخلايا) هنا * /}
              <RowLabel label={rowData.name} />
              {columns.map((col) => (
                <ماتريكسإينبوت 
                    مفتاح = {col.id} 
                    variantId={getVariantId(rowData, col)} 
                />
              ))}
            </div>
          );
        })}
      </div>
    </div>
  );
};

الخلاصة الرئيسية: تعمل المحاكاة الافتراضية على تقليل وقت التحميل الأولي من 3.5 ثانية إلى 0.2 ثانية لمجموعات البيانات الكبيرة. إنه غير قابل للتفاوض بالنسبة للكتالوجات التي تتجاوز 500 متغير.

المرحلة الثانية: إدارة الحالة (حل الذاكرة)

تحل المحاكاة الافتراضية مشكلة العرض، ولكن لا تزال لدينا مشكلة الحالة. إذا احتفظنا بحالة 10000 مُدخل في React State، فإن تحديث أحدها يتطلب تحسينًا دقيقًا لتجنب إطلاق تحديث على مستوى الشجرة.

نهج الإشارة

في Maison Code، نفضل الإشارات (عبر @preact/signals-react أو المشتركين التفصيليين البحتين) أو المكونات غير المنضبطة لشبكات المصفوفة.

إذا استخدمنا مكونات غير خاضعة للرقابة، فإننا نتجاوز دورة عرض React بالكامل لإجراء الكتابة.

  1. قراءة: defaultValue={data.get(id)}
  2. اكتب: onChange={(e) => data.set(id, e.target.value)} (الطفرة المباشرة أو تحديث المرجع)
  3. إرسال: اقرأ من المرجع/الخريطة.

ومع ذلك، نحتاج غالبًا إلى الحالة المحسوبة (على سبيل المثال، “الكمية الإجمالية: 150”). وهذا يعيدنا إلى عالم التفاعل.

أفضل أسلوب حديث هو Zustand with Transient Updates.

// store.ts
استيراد {إنشاء} من 'zustand'؛

واجهة ماتريكسستور {
  الكميات: سجل <سلسلة، رقم>؛
  setQuantity: (id: string, qty: number) => void;
  // القيم المحسوبة المشتقة من المكونات أو المحددات داخل المكونات
}

تصدير const useMatrixStore = create<MatrixStore>((set) => ({
    الكميات: {}،
    setQuantity: (id, qty) => set((state) => ({ 
        الكميات: { ...state.quantities, [id]: qty } 
    })))،
}));

// MatrixInput.tsx
// يشترك هذا المكون فقط في شريحة الحالة المحددة الخاصة به
const MatrixInput = ({variantId }) => {
    const qty = useMatrixStore((state) =>state.quantities[variantId] || 0);
    const setQuantity = useMatrixStore((state) =>state.setQuantity);

    العودة (
        <input 
            القيمة = {الكمية} 
            onChange={(e) => setQuantity(variantId, parseInt(e.target.value))} 
        />
    );
}

وهذا يضمن أن الكتابة في خلية واحدة فقط تعيد عرض تلك الخلية المحددة (وربما عداد “الإجمالي”)، بدلاً من الشبكة بأكملها.

المرحلة الثالثة: التفاعلات الدقيقة وتجربة المستخدم

السرعة تقنية، ولكنها أيضًا إدراكية. يتوقع مشتري B2B أن تعمل الأداة كجدول بيانات.

التنقل بلوحة المفاتيح

الواجهة المثقلة بالماوس بطيئة جدًا بحيث لا يمكن إدخالها بشكل مجمّع. يجب علينا تنفيذ ** التنقل باستخدام مفتاح السهم **.

  • أدخل: تحريك لأسفل (معيار جدول البيانات).
  • علامة التبويب: التحرك لليمين.
  • مفاتيح الأسهم: التحرك في الاتجاهات المعنية.

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

تعليقات فورية على المخزون

يجب ألا ينتظر المستخدمون “أضف إلى سلة التسوق” لمعرفة أن أحد المتغيرات غير متوفر في المخزون. نقوم بإحضار بيانات المخزون مسبقًا بتنسيق خفيف الوزن:

{
  "البديل_1": 50،
  "البديل_2": 0،
  "البديل_3": 1200
}

نحن نرسم هذا لحالة بصرية.

  • رمادي اللون: غير متوفر في المخزون (0).
  • تحذير أصفر: المخزون منخفض (كتب المستخدم 50، المخزون 40).
  • الحدود الحمراء: إدخال غير صالح.

يجب أن يتم التحقق من الصحة بشكل متزامن من جانب العميل.

المرحلة الرابعة: حمولة واجهة برمجة التطبيقات (API) والتجميع

يقوم المستخدم بالنقر على “أضف إلى الطلب”. لقد اختاروا 150 نوعًا فريدًا. معظم واجهات برمجة تطبيقات REST (بما في ذلك واجهة برمجة تطبيقات Cart القياسية الخاصة بـ Shopify) ليست مصممة لاستيعاب 150 عنصرًا في طلب HTTP POST واحد بكفاءة. قد تنتهي المهلة، أو تتجاوز حدود الحمولة.

الإستراتيجية: قائمة انتظار الوعد المجمعة

نحن لا نمنع واجهة المستخدم أبدًا. نعرض شريط التقدم (“إضافة عناصر… 40%”).

const BATCH_SIZE = 50؛

وظيفة غير متزامنة addToCartRecursive(items: Item[]) {
    إذا (items.length === 0) العودة؛
    
    قطعة ثابتة = items.slice(0, BATCH_SIZE);
    الكمية المتبقية = items.slice(BATCH_SIZE);
    
    // تحديث واجهة المستخدم المتفائل هنا
    
    حاول {
        انتظر api.cart.add(chunk);
        updateProgress((الإجمالي - الطول المتبقي) / الإجمالي);
        return addToCartRecursive(remaining); // الدفعة التالية
    } التقاط (خطأ) {
        HandlePartialFailure(chunk, error);
    }
}

الإستراتيجية: تحويل عربة التسوق (Shopify)

بالنسبة لتجار Shopify Plus، نستخدم وظائف تحويل سلة التسوق أو الحزم. يمكننا إضافة عنصر أصل مفرد (“The Matrix Bundle”) إلى سلة التسوق، والسماح لمنطق الواجهة الخلفية بتوسيعه إلى عناصر سطرية عند الدفع. يؤدي هذا إلى إبقاء تفاعلات سلة التسوق سريعة مع الحفاظ على منطق الواجهة الخلفية للتنفيذ. راجع دليلنا حول إمكانية توسيع الدفع لمعرفة المزيد حول هذا الأمر.

الجوال: المحور

شبكة 50x20 مستحيلة على الهاتف المحمول. لا تحاول أن تجعلها سريعة الاستجابة عن طريق تقليص الخلايا. إنه غير صالح للاستخدام.

على الهاتف المحمول، نقوم بتدوير واجهة المستخدم. بدلاً من الصفوف = الأحجام والأعمدة = الألوان، نعرض ببساطة السمة الأساسية (على سبيل المثال، اللون) كقائمة.

  • ينقر المستخدم على “الأحمر”.
  • يتوسع الأكورديون (أو تفتح الورقة السفلية).
  • يرى المستخدم قائمة بأحجام “الأحمر”.
  • كميات مدخلات المستخدم.
  • ينهار المستخدم “الأحمر” وينقر على “الأزرق”.

يحترم أسلوب “التنقل لأسفل” مساحة الشاشة الصغيرة مع الحفاظ على التسلسل الهرمي للبيانات سليمًا.

معايير الأداء

عندما قمنا بترحيل عميل رئيسي من نموذج React القياسي إلى مصفوفة Zustand الافتراضية، لاحظنا:

| متري | الشبكة القديمة (الرد القياسي) | مصفوفة كود ميزون (افتراضية) | تحسين | | :

قم بتوظيف مهندسينا.