MAISON CODE .
/ Tech · React · State Management · Architecture · Performance

إدارة الدولة: الذرية مقابل المنوليث

Redux ميت (في الغالب). السياق هو فخ. نظرة فنية عميقة إلى إدارة حالة React الحديثة: Zustand وJotai وTanStack Query.

AB
Alex B.
إدارة الدولة: الذرية مقابل المنوليث

في قلب كل تطبيق React تكمن “الحالة”. إذا كانت واجهة المستخدم عبارة عن دالة للحالة ($UI = f(state)$)، فإن إدارة تلك الحالة هي القرار الهندسي الأكثر أهمية الذي تتخذه.

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

في عام 2025، يكون النظام البيئي قد نضج. نقوم الآن بتصنيف الحالة إلى ثلاث طبقات متميزة، لكل منها أداة متخصصة.

لماذا يناقش Maison Code هذا الأمر

لقد رأينا إعادة عرض تطبيق العميل 4000 مرة في الثانية بسبب موفر سياق واحد. لقد نقلناها إلى بنية الحالة ثلاثية الطبقات:

  • حالة الخادم: استعلام TanStack (للتخزين المؤقت والترطيب).
  • حالة العميل العالمية: Zustand (لسلة التسوق والموضوع).
  • الحالة المحلية: الإشارات أو حالة الاستخدام (لمدخلات النموذج). أدى هذا إلى تقليل وقت حظر الخيط الرئيسي بمقدار 600 مللي ثانية وتحسين نتائج INP إلى أقل من 50 مللي ثانية. نعتقد أن إدارة الحالة هي السبب الأول لتوقف الأداء في تطبيقات React.

النظرية: الطبقات الثلاث

1. حالة الخادم (ذاكرة التخزين المؤقت)

البيانات التي تعيش على خادم بعيد. أنت لا “تملكها”؛ أنت “تقترض” ذلك. يمكن أن تصبح قديمة.

  • أمثلة: ملف تعريف المستخدم، قائمة المنتجات، الطلبات.
  • الأداة: TanStack Query (React Query) أو SWR.
  • مكافحة النمط: وضع بيانات API في Redux/Zustand يدويًا. ينتهي بك الأمر إلى إعادة اختراع حالات التخزين المؤقت وإلغاء البيانات المكررة والتحميل.

2. حالة العميل (واجهة المستخدم)

البيانات الموجودة فقط في المتصفح ويتم مشاركتها عبر المكونات.

  • أمثلة: هل النموذج مفتوح؟ ما هو الموضوع الحالي؟ ماذا يوجد في سلة التسوق؟
  • الأداة: Zustand (للمتاجر العالمية) أو Jotai (للتحديثات الذرية).

3. الحالة المحلية (المكون)

البيانات معزولة إلى مكون واحد أو أبنائه المباشرين.

  • أمثلة: هل القائمة المنسدلة مفتوحة؟ ما هي قيمة حقل الإدخال المحدد هذا؟
  • الأداة: useState / useReducer.

مشكلة السياق

لماذا لا تستخدم “createContext” فقط في كل شيء؟ بسبب التفاعل الخشن الحبيبات.

const DataContext = createContext();

موفر الوظيفة({ أطفال }) {
  const [name, setName] = useState("Alex");
  const [theme, setTheme] = useState("Dark");
  
  // يتغير مرجع الكائن هذا عند أي تحديث
  قيمة const = { name, theme, setName, setTheme }; 

  إرجاع <DataContext.Provider value={value}>{children}</DataContext.Provider>؛
}

إذا تم استدعاء setName("John"):

  1. يتم إعادة إنشاء كائن “القيمة”.
  2. يُعاد عرض كل مكون يستدعي useContext(DataContext).
  3. حتى مكون ThemeToggler يُعاد عرضه، على الرغم من أن theme لم يتغير!

يمكنك تحسين ذلك باستخدام useMemo وتقسيم السياقات (NameContext، ThemeContext)، ولكن هذا يؤدي إلى “Context Hell” (هرم الموت).

الحل 1: Zustand (بديل Monolith)

Zustand هو تقريبًا “Redux بدون النموذج المعياري.” ويستخدم بنية المتجر، ولكنه يحل مشكلة إعادة العرض باستخدام المحددات.

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

const useStore = create((set) => ({
  الدببة: 0،
  الأسماك: 0،
  زيادة الدببة: () => مجموعة((الحالة) => ({ الدببة: الحالة. الدببة + 1 }))),
  زيادة السمك: () => set((state) => ({ الأسماك:state.fish + 1 }))),
}));

الدالة BearCounter() {
  // المحدد: هذا المكون يراقب "الدببة" فقط
  const Bears = useStore((state) =>state.bears);
  إرجاع <h1>{الدببة} الدببة</h1>;
}

إذا تم استدعاء increaseFish()، فلن يقوم BearCounter بإعادة العرض. يقوم Zustand بمقارنة القيمة المرجعة للمحدد (state.bears). إذا لم يتغير، فإنه يتخطى التحديث.

قوة البرمجيات الوسيطة

يبلغ حجم Zustand في الغالب 1 كيلو بايت، ولكنه برنامج وسيط قوي. الاستمرار: يحفظ الحالة تلقائيًا في “التخزين المحلي”. Immer: يسمح ببناء الجملة القابل للتغيير (state.bears++) في التحديثات.

استيراد { استمرار } من 'zustand/middleware'؛

const useCartStart = إنشاء(
  يستمر(
    (مجموعة) => ({
      العناصر: []،
      addItem: (id) => set((state) => ({ items: [...state.items, id] }))),
    }),
    { name: 'cart-storage' } // أدخل التخزين المحلي
  )
);

الوصول إلى المكونات الخارجية

والأهم من ذلك، أنه يمكن الوصول إلى متاجر Zustand خارج React (على سبيل المثال، في وظائف المرافق أو اعتراضات واجهة برمجة التطبيقات).

// api.ts
استيراد { useAuthStore } من './authStore'؛

رمز const = useAuthStore.getState().token; // وصول بدون ربط
fetch('/api', { headers: { Authorization: token } });

الحل 2: جوتاي (النهج الذري)

Jotai (المستوحى من Recoil) يتبع نهجًا مختلفًا. بدلاً من متجر واحد كبير، لديك الآلاف من الذرات الصغيرة. الدولة تُبنى من الأسفل إلى الأعلى.

استيراد { atom, useAtom } من 'jotai';

// أعلن الذرات
سعر ثابتAtom = atom(10);
كمية ثابتةAtom = atom(2);

// المحسوبة (المشتقة) الذرة
const TotalAtom = atom((get) => get(priceAtom) * get(quantityAtom));

عربة الوظائف () {
  const [total] = useAtom(totalAtom);
  إرجاع <div>الإجمالي: {total</div>؛ 
}

حالة الاستخدام: تطبيقات تفاعلية للغاية مثل جدول البيانات أو محرر المخططات أو لوحة المعلومات حيث تكون التبعيات معقدة. إذا قمت بتحديث “priceAtom”، فسيتم إعادة عرض المكونات التي تستمع إلى “price” أو “total”. إنها الدقة الجراحية.

حالة الخادم: التعامل مع غير المتزامن

نحن نؤيد بشدة TanStack Query. يتعامل مع الأجزاء الصعبة من حالة المزامنة:

  • Stale-While-Revalidate: إظهار البيانات القديمة أثناء جلب البيانات الجديدة.
  • إعادة جلب التركيز: قم بتحديث البيانات عندما يعود المستخدم إلى النافذة.
  • إلغاء البيانات المكررة: إذا طلبت 10 مكونات ملف تعريف المستخدم، فسيتم تقديم طلب شبكة واحد فقط.
const { data, isLoading } = useQuery({
  مفتاح الاستعلام: ["المستخدم"، المعرف]،
  queryFn: () => fetchUser(id)،
  staleTime: 1000 * 60, // البيانات حديثة لمدة دقيقة واحدة
});

يعد خلط هذا مع Zustand أمرًا شائعًا. Zustand يحمل حالة “التصفية”. يحتفظ React Query ببيانات “القائمة” المشتقة من هذا الفلتر.

ملخص: مصفوفة القرار

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

10. الإشارات: المستقبل؟ (ما قبل/صلب)

React يعيد عرض المكونات. تعمل الإشارات (المعتمدة بواسطة Preact وSolid وVue وAngular) على تحديث DOM مباشرة. عدد ثابت = إشارة (0)؛ <div>{count</div> عند حدوث count.value++، لا يتم إعادة تشغيل وظيفة المكون. يتم تحديث العقدة النصية فقط في DOM. هذا هو تعقيد O(1). تستكشف React هذا الأمر باستخدام “React Compiler” (انسى)، لكن الإشارات هي أداة بدائية أكثر كفاءة بشكل أساسي للتفاعلات الدقيقة. نحن نراقب هذا الفضاء عن كثب. بالنسبة للوحات المعلومات عالية الأداء (تداول العملات المشفرة)، نستخدم أحيانًا Preact + Signals بدلاً من React.

11. الحالة المستمرة (محلي-أولاً)

Zustand “الاستمرار” بسيط. ولكن ماذا عن بيانات Offline-First الصالحة؟ نحن نتجه نحو البرمجيات المحلية أولاً (LoFi). أدوات مثل RxDB أو Replicache. “مدير الحالة” هو في الواقع قاعدة بيانات محلية تتم مزامنتها في الخلفية. وهذا يعامل الخادم باعتباره مصدرًا “ثانويًا” للحقيقة. العميل أساسي. هذه البنية تجعل التطبيقات تبدو فورية (زمن الوصول 0 مللي ثانية).

13. نمط المراقب (MobX)

قبل الإشارات، كان هناك MobX. ويستخدم كائنات “يمكن ملاحظتها” و”مراقبون” (مكونات). إنه شعور مثل السحر. يمكنك تغيير كائن user.name = "John"، وتحديث المكونات. لا نوصي باستخدام MobX للمشاريع الجديدة لأنه يخفي قدرًا كبيرًا من التعقيد (الوكلاء السحريون). من الصعب جدًا تصحيح أخطاء “لماذا تم عرض هذا؟”. ومع ذلك، بالنسبة لتطبيقات محددة للغاية وكثيفة البيانات (مثل جداول البيانات)، يكون MobX أسرع من Redux بسبب تتبع التبعية الدقيق الخاص به.

14. مجموعة أدوات الإعادة (RTK): التحديث

إذا كان يجب عليك استخدام Redux (Enterprise Legacy)، فاستخدم RTK. يتصرف مثل Zustand.

  • لا توجد بيانات “التبديل”.
  • المدمج في Immer (بناء الجملة القابل للتغيير).
  • استعلام RTK مدمج ** (على غرار استعلام TanStack). يؤدي الترحيل من Redux القديم إلى RTK إلى تقليل حجم الكود بنسبة 60%. ولكن إذا كنت تبدأ من جديد: Zustand هو 1 كيلو بايت، وRTK هو 40 كيلو بايت. اختر بحكمة.

15. تكريم الشهداء: الارتداد

يجب أن نذكر ** الارتداد ** (ميتا). لقد اخترعت مفهوم “Atom” لـ React. لكن لا تتم صيانته حاليًا (تم نقل Meta إلى أدوات داخلية أخرى). ** جوتاي ** التقط الشعلة. إذا كان لديك قاعدة بيانات Recoil، فانتقل إلى Jotai. واجهة برمجة التطبيقات متشابهة بنسبة 90% (useRecoilState -> useAtom). لا تبدأ مشاريع جديدة باستخدام Recoil.

16. الاستنتاج

لم تعد إدارة الدولة تتمحور حول إيجاد “المكتبة الواحدة لحكمهم جميعًا”. يتعلق الأمر بتأليف أدوات متخصصة. في Maison Code، نستخدم بشكل افتراضي مجموعة Zustand + React Query. إنها قوية وفعالة وصديقة للمطورين.


إعادة هيكلة الإرث القديم؟

هل تطبيقك مدفون تحت النموذج المعياري والمخفضات البطيئة؟

قم بتعيين مهندسينا المعماريين.