Suspense چیست: نگاهی مستقل و کامل

در فصل بیستم، Suspense را فقط در یک کاربرد مشخص دیدیم: نمایش یک حالت موقت تا پایان دانلود یک کامپوننت lazy. در این فصل، به‌طور کامل و مستقل به خود Suspense می‌پردازیم؛ چون همان‌طور که در پایان فصل قبل اشاره کردیم، این مکانیزم فراتر از Code Splitting است و مدل ذهنی عمیق‌تری دارد که این درس، پایه آن را می‌سازد.

مسئله‌ای که Suspense حل می‌کند: «هنوز آماده نیست»

در طول این دوره، چند بار با وضعیت‌هایی روبه‌رو شدیم که یک کامپوننت، برای Render شدن، باید منتظر چیزی بماند: در فصل چهاردهم منتظر پاسخ یک درخواست شبکه، و در فصل بیستم منتظر دانلود کد یک ماژول. در هر دو مورد، تا پیش از معرفی الگوهای دستی که دیدیم (State جداگانه برای Loading)، React راه رسمی و یکپارچه‌ای برای بیان «این بخش هنوز آماده نیست» نداشت.

Suspense: یک مرز برای وضعیت‌های ناآماده

Suspense، یک کامپوننت است که دور بخشی از درخت قرار می‌گیرد و می‌گوید: «اگر هرکدام از فرزندانم هنوز آماده Render شدن نیستند، به‌جای آن‌ها، این fallback را نشان بده». این دقیقاً همان جمله‌ای است که در فصل بیستم برای lazy به کار بردیم، اما حالا می‌خواهیم ببینیم این ایده، چرا محدود به lazy نیست.

jsx
<Suspense fallback={<Spinner />}>
  <SomeComponent />
</Suspense>

مقایسه با مدل دستی قدیمی

در مدل دستی که در فصل چهاردهم ساختیم، هر کامپوننتی که داده می‌گرفت، خودش مسئول نگه‌داشتن یک State به نام loading و نوشتن یک if (loading) return ... بود:

jsx
function Profile() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  // ...
  if (loading) return <p>در حال بارگذاری...</p>;
  return <p>{data.name}</p>;
}

با Suspense، ایده این است که خود کامپوننت Profile دیگر لازم نیست این منطق را بنویسد؛ به‌جای آن، اگر داده‌اش آماده نباشد، کامپوننت می‌تواند وضعیت «هنوز آماده نیستم» را به بیرون اعلام کند، و نزدیک‌ترین Suspense، مسئولیت نمایش حالت بارگذاری را به عهده می‌گیرد.

چرا این جدا‌سازی مهم است

این جداسازی، شبیه همان اصلی است که در فصل هفدهم با Error Boundary دیدیم: به‌جای این‌که هر کامپوننت، خودش منطق مدیریت خطا را تکرار کند، یک مرز بیرونی، این مسئولیت را از آن جدا می‌کند. Suspense همین کار را برای «ناآماده بودن» انجام می‌دهد: منطق نمایش Loading، از منطق خود کامپوننت جدا و در یک مرز مشترک، متمرکز می‌شود.

Suspense، خودش داده‌ای تولید نمی‌کند

یک نکته مهم که باید روشن باشد: خود Suspense، هیچ داده‌ای نمی‌گیرد و هیچ Loading State ای مدیریت نمی‌کند. Suspense فقط یک ناظر است که منتظر می‌ماند تا کامپوننت‌های داخلش، اعلام کنند آماده‌اند یا نه. مکانیزم دقیق این‌که یک کامپوننت چگونه «ناآماده بودن» خودش را اعلام می‌کند (همان پرتاب Promise که در پایان فصل بیستم دیدیم)، و این‌که کدام منابع داده از این مکانیزم پشتیبانی می‌کنند، موضوع درس‌های بعدی همین فصل است.

چرا Suspense هنوز جایگزین کامل الگوی دستی فصل چهاردهم نیست

نکته مهمی که باید از همین ابتدا روشن شود: برای این‌که یک کامپوننت بتواند از Suspense استفاده کند، منبع داده آن (مثلاً یک کتابخانه دریافت داده) باید از این مکانیزم پشتیبانی کند؛ خود fetch خام، به‌تنهایی، چنین قابلیتی ندارد. به همین دلیل، الگوی دستی Loading/Error که در فصل چهاردهم ساختیم، همچنان کاملاً معتبر و رایج است؛ Suspense، یک جایگزین اختیاری و پیشرفته‌تر است، نه یک جایگزین اجباری برای همه موارد.

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

در این مثال، همان ایده پایه‌ای Suspense، با یک کامپوننت lazy (تنها کاربردی که تا اینجا دیدیم) دوباره مرور می‌شود تا تصویر کلی این درس، عینی باقی بماند.

Suspense به‌عنوان یک مرز برای وضعیت ناآماده
import { Suspense, lazy } from "react";

const SlowComponent = lazy(() => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ default: () => <p>این بخش آماده شد.</p> });
    }, 1200);
  });
});

function App() {
  return (
    <div>
      <p>این بخش همیشه فوری نمایش داده می‌شود.</p>
      <Suspense fallback={<p>در حال آماده شدن...</p>}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

Suspense، یک مرز است که منطق نمایش حالت «هنوز آماده نیست» را از خود کامپوننت‌ها جدا و متمرکز می‌کند؛ این ایده، شبیه نقشی است که Error Boundary برای خطاها دارد. Suspense خودش داده‌ای مدیریت نمی‌کند و فقط زمانی کار می‌کند که منبع داده کامپوننت، از این مکانیزم پشتیبانی کند. در درس بعدی، به Suspense Boundary و رفتار دقیق‌تر آن می‌پردازیم.