هندسة مستقبل الـ Local-First: بناء تطبيقات تدعم العمل دون اتصال باستخدام Expo و TanStack Query
تعمق في بناء تطبيقات جوال مرنة تدعم العمل دون اتصال (offline-ready) باستخدام منظومة Expo الحديثة وطبقات الاستمرارية في TanStack Query. تعلم كيف تسد الفجوة بين الحالة المؤقتة والتخزين المحلي الدائم.

لسنوات، تعاملنا مع تطبيقات الجوال كمجرد واجهات رقيقة (thin wrappers) حول API. إذا انقطع اتصال الـ Wi-Fi، يبدأ مؤشر التحميل (spinner) بالدوران بلا نهاية، وتتوقف تجربة المستخدم تماماً. لكن في مشاريعي الأخيرة، أصبحت مهووساً بالابتعاد عن هذا الاعتماد الكلي على الـ cloud-first. نحن ندخل عصر بنية Local-First architecture، حيث يكون الجهاز المحلي هو المصدر الأساسي للحقيقة، والشبكة مجرد طبقة مزامنة غير متزامنة (asynchronous synchronization layer).
عندما نبني باستخدام Expo، فنحن نمتلك ميزة هائلة. لكن لحظة الإدراك الحقيقية بالنسبة لي كانت عندما اكتشفت كيفية دفع TanStack Query (v5) إلى ما هو أبعد من مجرد ذاكرة تخزين مؤقت بسيطة (memory caching) وتحويله إلى محرك استمرارية (persistence engine) متكامل.
إليك كيف أقوم بهندسة هذه الأنظمة لتصمد في أنفاق المترو وأوضاع الطيران في العالم الحقيقي.
التحول: من مجرد Cache إلى مخزن بيانات (Store)
يستخدم معظم المهندسين TanStack Query لمميزاته الرائعة في hooks مثل useQuery و useMutation ، لكنهم يتعاملون مع الـ cache كبيانات متطايرة (volatile). للانتقال إلى local-first، نحتاج لهذا الـ cache أن يظل موجوداً حتى بعد إغلاق التطبيق أو قيام نظام التشغيل بمسح الذاكرة.
في تجربتي، المزيج بين react-native-mmkv (من أجل السرعة) و persistQueryClient الخاص بـ TanStack هو الصيغة الرابحة. مكتبة MMKV أسرع بكثير من AsyncStorage لأنها مكتبة C++ متزامنة، وهو أمر بالغ الأهمية عندما تقوم باستعادة حالة ضخمة (hydrating large state) عند تشغيل التطبيق.
الخطوة 1: إعداد الـ Persistent Persister
أولاً، نحتاج لربط الـ client ليتواصل مع القرص (disk). أفضّل إنشاء persister مخصص يتعامل مع عملية التسلسل (serialization).
الخطوة 2: مدير الاتصال (The Online Manager)
يحتاج TanStack Query لمعرفة ما إذا كان الجهاز متصلاً بالإنترنت فعلياً. في منظومة Expo، تعتبر مكتبة @react-native-community/netinfo هي المعيار الذهبي. أقوم دائماً بوضعها داخل مستمع (listener) حتى يتمكن queryClient من استئناف العمليات المعلقة (mutations) تلقائياً عند عودة الإشارة.
الخطوة 3: واجهة مستخدم متفائلة (Optimistic UI) – السر الحقيقي
في تطبيقات local-first، لا ينبغي لـ UI أن تنتظر الـ server أبداً. عندما يضغط المستخدم على 'إعجاب' أو 'إرسال'، يجب أن ينعكس التغيير فوراً. نحقق ذلك عن طريق تحديث الـ cache يدوياً في callback الـ onMutate ، والتراجع عنه إذا رفض الـ server العملية لاحقاً.
هنا يقع العديد من المطورين في الخطأ. يجب عليك التعامل مع منطق 'الإلغاء' (Cancel logic) لضمان أن أي جلب بيانات (refetch) صادر لا يمسح تحديثك المتفائل.
لماذا يهم هذا مهندسي Expo؟
من خلال الاستفادة من PersistQueryClientProvider ، نضمن أنه حتى لو أغلق المستخدم التطبيق، فإن الـ mutations المعلقة (التي لم تصل إلى الخادم بعد) يتم تخزينها ويمكن إعادة محاولتها لاحقاً. هذا يحول 'خطأ الشبكة' من مجرد فشل إلى مهمة 'مزامنة في الخلفية' (Background Sync).
الحقائق الصعبة
الهندسة لنمط local-first ليست مجانية. عليك التعامل مع حل النزاعات (Conflict Resolution). إذا قام مستخدم بتعديل مستند دون اتصال على جهاز iPhone وجهاز iPad في نفس الوقت، فأيهما يفوز؟ بالنسبة لمعظم التطبيقات التي أبنيها، قاعدة 'آخر كتابة تفوز' (Last Write Wins) تكون كافية، ولكن للأدوات التعاونية المعقدة، ستحتاج للنظر في CRDTs (Conflict-free Replicated Data Types).
كلمات أخيرة
الانتقال إلى بنية مستمرة (persistent architecture) باستخدام Expo و TanStack Query غيّر جذرياً طريقة تعاملي مع تطوير الجوال. لم يعد الأمر يتعلق بإدارة حالات التحميل (loading states)؛ بل أصبح يتعلق بإدارة مزامنة البيانات. النتيجة؟ تطبيقات تشعر بسلاسة فائقة، استجابة فورية، والأهم من ذلك—موثوقية عالية في عالم لا تتوفر فيه شبكات 5G دائماً.
إذا لم تجرب حفظ الـ query cache الخاص بك بعد، فابدأ من هناك. سيشكرك مستخدموك على اختفاء مؤشرات التحميل المستمرة.