Client State در برابر Server State: چرا نباید با یک ابزار مدیریت شوند

در فصل چهاردهم، با جزئیات کامل دیدیم Server State (داده‌ای که سرور صاحب واقعی آن است) چه تفاوتی با Client State دارد. در این درس، همان تمایز را دقیقاً در زمینه مدیریت State در مقیاس بزرگ بازمی‌بینیم: چرا تلاش برای مدیریت هر دو نوع با یک ابزار واحد، معمولاً منجر به مشکل می‌شود.

یادآوری سریع تفاوت

Client State (مثل باز بودن یک منو یا مرحله فعلی یک ویزارد) فقط در اختیار React است. Server State (مثل لیست سفارش‌های کاربر) یک کپی لحظه‌ای از داده‌ای است که جای دیگری، روی سرور، نگه‌داری می‌شود و همیشه ممکن است کهنه باشد.

اشتباه رایج: ریختن همه‌چیز در یک Store واحد

وقتی یک برنامه بزرگ می‌شود، وسوسه رایجی پیش می‌آید: ساختن یک Store بزرگ Global (با Context یا یک کتابخانه مدیریت State) که هم لیست محصولات دریافتی از سرور را نگه می‌دارد، هم وضعیت باز یا بسته بودن یک Modal را.

jsx
const store = {
  products: [], // از سرور آمده
  isModalOpen: false, // فقط محلی
  user: null, // از سرور آمده
  selectedTab: "home", // فقط محلی
};

مشکل این ساختار این است که دو نوع داده با نیازهای کاملاً متفاوت، در یک‌جا قاطی شده‌اند. Server State به چیزهایی مثل Caching، Revalidation و مدیریت Loading/Error نیاز دارد که در فصل چهاردهم دیدیم؛ Client State اصلاً به هیچ‌کدام از این‌ها نیاز ندارد.

چرا این ترکیب در عمل مشکل‌ساز می‌شود

اگر از یک ابزار ساده مدیریت Client State برای نگه‌داشتن Server State هم استفاده شود، معمولاً یکی از این اتفاقات می‌افتد: یا منطق Caching و Revalidation به‌طور دستی و ناقص دوباره نوشته می‌شود (همان مشکلی که در فصل چهاردهم دیدیم)، یا این نیازها اصلاً نادیده گرفته می‌شوند و برنامه، داده‌های کهنه را بدون تشخیص نمایش می‌دهد.

راه‌حل: دو ابزار جدا، برای دو نوع نیاز جدا

رویکرد رایج‌تر در برنامه‌های واقعی React، استفاده از دو ابزار مجزا است: یک کتابخانه تخصصی Server State (که در فصل چهاردهم به آن اشاره کردیم) برای داده دریافتی از سرور، و یک ابزار ساده‌تر Client State (مثل useState، useReducer یا Context) برای چیزهایی که فقط در مرورگر کاربر معنا دارند.

jsx
// Server State: با کتابخانه تخصصی
const { data: products } = useQuery(["products"], fetchProducts);

// Client State: با ابزار ساده محلی
const [isModalOpen, setIsModalOpen] = useState(false);

یک معیار سریع برای تشخیص محل درست هر مقدار

همان سؤالی که در فصل چهاردهم مطرح کردیم، اینجا هم راهنماست: اگر صفحه را رفرش کنیم و این مقدار از بین برود، آیا باید دوباره از سرور گرفته شود؟ اگر بله، جایش در ابزار Server State است. اگر مقدار فقط برای همین Session معنا دارد (مثل تب فعال یک تنظیمات)، جایش در ابزار ساده Client State است.

مثال قابل اجرا

در این مثال، یک وضعیت محلی (باز بودن پنل) و یک داده فرضی از سرور، عمداً جدا از هم نگه داشته شده‌اند.

جدا نگه‌داشتن Client State از Server State
import { useState, useEffect } from "react";

function fakeFetchProducts() {
  return new Promise((resolve) => setTimeout(() => resolve(["کیبورد", "ماوس"]), 800));
}

function App() {
  // Client State
  const [isPanelOpen, setIsPanelOpen] = useState(false);

  // Server State (شبیه‌سازی‌شده)
  const [products, setProducts] = useState(null);

  useEffect(() => {
    fakeFetchProducts().then(setProducts);
  }, []);

  return (
    <div>
      <button onClick={() => setIsPanelOpen(!isPanelOpen)}>نمایش/پنهان پنل</button>
      {isPanelOpen && <p>این یک پنل کاملاً محلی است.</p>}
      <p>محصولات: {products ? products.join("، ") : "در حال بارگذاری..."}</p>
    </div>
  );
}
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

ریختن Client State و Server State در یک Store واحد، معمولاً یا نیازهای خاص Server State (مثل Caching) را نادیده می‌گیرد یا باعث بازسازی دستی و ناقص آن‌ها می‌شود. راه‌حل رایج، استفاده از دو ابزار جدا برای این دو نوع نیاز است. در درس بعدی، به این می‌پردازیم که اصلاً چه زمانی به یک ابزار مدیریت State نیاز داریم.