ارتباط عمیق‌تر Code Splitting و Suspense

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

یادآوری: چرا اصلاً Suspense لازم بود

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

مکانیزم پشت صحنه: پرتاب یک Promise

نکته‌ای که معمولاً دیده نمی‌شود این است که وقتی یک کامپوننت lazy هنوز آماده نیست، React در پشت صحنه، همان Promise مربوط به دانلود آن ماژول را، به‌شکلی مشابه پرتاب یک خطا (که در فصل هفدهم با Error Boundary دیدیم)، به بالا می‌فرستد. نزدیک‌ترین Suspense، این Promise را می‌گیرد، منتظر حل شدنش می‌ماند، و دوباره تلاش می‌کند همان بخش را Render کند. این شباهت ساختاری با Error Boundary تصادفی نیست؛ هر دو، مکانیزمی برای مدیریت یک وضعیت غیرعادی در جریان Render هستند، یکی برای خطا و دیگری برای «هنوز آماده نیست».

یک Suspense، می‌تواند چند کامپوننت lazy را هم‌زمان بپوشاند

jsx
<Suspense fallback={<p>در حال بارگذاری...</p>}>
  <ChartWidget />
  <TableWidget />
</Suspense>

اگر هر دوی ChartWidget و TableWidget با lazy تعریف شده باشند، همین یک Suspense، تا وقتی هر دو آماده شوند، همان یک fallback را نشان می‌دهد؛ React صبر می‌کند تمام محتوای داخل یک مرز Suspense، با هم و هماهنگ، آماده شوند، نه این‌که یکی زودتر و دیگری دیرتر، به‌شکل پراکنده ظاهر شود.

مرزهای تودرتوی Suspense

می‌توان چند Suspense را تودرتو کرد تا بخش‌های مختلف یک صفحه، با سرعت‌های متفاوت ظاهر شوند؛ شبیه همان ایده‌ای که در فصل هفدهم، درباره چند Error Boundary مستقل برای مرزبندی خطا دیدیم:

jsx
<Suspense fallback={<PageSkeleton />}>
  <MainContent />
  <Suspense fallback={<p>در حال بارگذاری نظرات...</p>}>
    <CommentsSection />
  </Suspense>
</Suspense>

در این ساختار، اگر CommentsSection کندتر از MainContent آماده شود، فقط همان بخش داخلی، fallback خودش را نشان می‌دهد؛ بقیه صفحه منتظر آن نمی‌ماند.

چرا Suspense فقط مخصوص Code Splitting نیست

مهم است این نکته روشن باشد که Suspense، به‌خودی‌خود، مفهومی عمومی‌تر از فقط «منتظر دانلود یک فایل» است؛ همان مکانیزم، در React مدرن، برای مدیریت دریافت داده هم به کار می‌رود (موضوعی که در فصل بیست‌ویکم بررسی می‌شود). در این فصل، فقط با یک کاربرد مشخص از Suspense، یعنی ترکیب با lazy، آشنا شدیم.

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

در این مثال، دو مرز Suspense تودرتو، با سرعت‌های متفاوت، رفتار مستقل خودشان را نشان می‌دهند.

دو مرز Suspense تودرتو با زمان بارگذاری متفاوت
import { lazy, Suspense } from "react";

function delayedModule(text, ms) {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ default: () => <p>{text}</p> }), ms);
  });
}

const MainContent = lazy(() => delayedModule("محتوای اصلی آماده شد", 600));
const CommentsSection = lazy(() => delayedModule("نظرات آماده شد", 2000));

function App() {
  return (
    <Suspense fallback={<p>در حال بارگذاری کل صفحه...</p>}>
      <MainContent />
      <Suspense fallback={<p>در حال بارگذاری نظرات...</p>}>
        <CommentsSection />
      </Suspense>
    </Suspense>
  );
}
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

Suspense، با گرفتن یک Promise که از یک کامپوننت lazy ناآماده بالا می‌آید، تا حل شدن آن، یک fallback نشان می‌دهد؛ این مکانیزم به ساختار Error Boundary در فصل هفدهم شباهت دارد، اما برای وضعیت «هنوز آماده نیست». چند مرز Suspense تودرتو، اجازه می‌دهند بخش‌های مختلف یک صفحه، مستقل از هم، با سرعت خودشان ظاهر شوند. با این درس، فصل بیستم به پایان می‌رسد و در ادامه به آزمون فصل ۲۰ می‌رویم.