التشغيل · فصل ٨

من غير React ومع السيرفر

createStore · useStore · context · SSR
🎯 الهدف: تعمل متجر مستقل، وتديه لكل شجرة، وتختبره من غير رسم.

createStore من zustand/vanilla متجر من غير هوك. عليه نفس getState و setState و subscribe. useStore يوصّل المكوّن بالمتجر ده مع سليكتور.

vanilla.jsx
import { createStore } from "zustand/vanilla";import { useStore } from "zustand";const cartStore = createStore((set) => ({  items: [],  add: (item) => set((state) => ({ items: [...state.items, item] })),}));function Count() {  const count = useStore(cartStore, (state) => state.items.length);  return <p>{count}</p>;}

في الإصدار الخامس useStore يتصدر من zustand. مسار الاستيراد القديم اتغير، فراجع الدوكس.

متجر لكل شجرة

createContext بتاع zustand/context اتوقف في الإصدار الرابع واتشال في الخامس. البديل سياق React عادي، والمتجر vanilla جواه.

اعمل المتجر مرة في ref، مش كل رسم. البذرة تيجي من props: رقم مستخدم، أو عناصر جاية من السيرفر.

scoped.jsx
import { createContext, useContext, useRef } from "react";import { createStore } from "zustand/vanilla";import { useStore } from "zustand";const CartContext = createContext(null);function createCartStore(userId) {  return createStore(() => ({ userId, items: [] }));}function CartProvider({ userId, children }) {  const ref = useRef(null);  if (!ref.current) ref.current = createCartStore(userId);  return <CartContext.Provider value={ref.current}>{children}</CartContext.Provider>;}function useCart(selector) {  const store = useContext(CartContext);  return useStore(store, selector);}

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

الترطيب

HTML السيرفر لازم يطابق أول رسم على العميل. متقراش localStorage أو window وأنت بترسم. persist مع skipHydration يخلّي أول رسم فاضي، و rehydrate في useEffect يملا بعد ما الصفحة تمسك.

في Next، حالة الواجهة العامة ممكن متجر موديول. حالة خاصة بالطلب تتعمل جوه المزود زي المثال اللي فات.

الاختبار

من غير رسم: نادِ الأكشن على getState واقرا الناتج. بين الاختبارات رجّع getInitialState مع استبدال، عشان الاختبار اللي فات ما يسيّبش عناصر.

test.js
beforeEach(() => {  useCart.setState(useCart.getInitialState(), true);});test("adds", () => {  useCart.getState().add({ id: 1, name: "قلم" });  expect(useCart.getState().items).toHaveLength(1);});

اختبار المكوّن يرسمه بمكتبة React، والسليكتور يقرا نفس المتجر. لو المتجر متشارك، الرجوع للأول قبل كل اختبار يمنع التسريب.

خلّصت الفصل لما

🧠 اختبر نفسك
١) createContext بتاع المكتبة لسه موجود؟
لا في الإصدار الخامس. السياق من React، والمتجر vanilla جواه.
٢) ليه المتجر مش جوه دالة المكوّن كل رسم؟
عشان الحالة تتمسح. ref يمسكه مرة، والرسم يعيد استخدامه.
٣) اختبارين على نفس المتجر. التاني شاف عناصر الأول. تظبطها إزاي؟
setState بالحالة الأولى واستبدال true قبل كل اختبار.