محدودیت‌ها و مدل ذهنی نهایی Suspense

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

محدودیت اول: نیاز به منبع سازگار

همان‌طور که چند بار در این فصل تکرار شد، Suspense فقط با چیزهایی کار می‌کند که بدانند چگونه یک Promise را در حین Render پرتاب کنند. lazy این کار را برای بارگذاری کد انجام می‌دهد؛ کتابخانه‌های تخصصی Server State، آن را برای داده انجام می‌دهند. اما یک fetch خام داخل یک useState/useEffect معمولی، هرگز خودش Suspend نمی‌شود؛ صرف داشتن یک Suspense در درخت، به‌تنهایی هیچ رفتاری را تغییر نمی‌دهد اگر منبع داده‌اش سازگار نباشد.

محدودیت دوم: Suspense، جایگزین مدیریت خطا نیست

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

محدودیت سوم: رفتار پیش‌فرض در Update، همیشه ایده‌آل نیست

همان‌طور که در درس دوم دیدیم، وقتی محتوایی که از قبل روی صفحه بوده، دوباره Suspend شود، رفتار پیش‌فرض، پرش فوری به fallback است؛ نه حفظ محتوای قبلی. کنترل این رفتار، نیاز به ابزار دیگری (useTransition) دارد که در فصل بعدی این دوره می‌بینیم.

محدودیت چهارم: نوشتن دستی منبع سازگار، پرریسک است

مثال createResource که در دو درس قبل دیدیم، فقط برای فهم مکانیزم بود؛ نسخه واقعی و قابل‌اعتماد چنین چیزی، باید Caching، Race Condition، و Cleanup را هم درست مدیریت کند، دقیقاً همان مسائلی که در فصل چهاردهم با جزئیات دیدیم. نوشتن دستی این الگو برای یک پروژه واقعی، توصیه نمی‌شود.

مدل ذهنی نهایی، در یک جمله

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

یک نقشه راه برای تصمیم‌گیری عملی

  • برای بارگذاری کد یک کامپوننت بزرگ → lazy همراه با Suspense (فصل بیستم و این فصل).
  • برای دریافت داده در پروژه‌های کوچک یا هنگام یادگیری → الگوی دستی فصل چهاردهم، همچنان کاملاً معتبر.
  • برای دریافت داده در پروژه‌های بزرگ‌تر → یک کتابخانه تخصصی که از Suspense پشتیبانی می‌کند (فصل شانزدهم).
  • برای هر دو مورد بالا → همیشه یک Error Boundary، بیرون از Suspense، در کنارشان قرار گیرد.

ارتباط این فصل با آنچه در ادامه می‌آید

در فصل بعدی، به Concurrent Rendering و useTransition می‌پردازیم که مستقیماً روی رفتار Suspense در Update اثر می‌گذارند. در فصل بیست‌وسوم، با use، یک راه رسمی‌تر برای خواندن Promise آشنا می‌شویم. و در فصل بیست‌وچهارم، Suspense را در معماری Server Components، در مقیاسی بسیار بزرگ‌تر، دوباره می‌بینیم.

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

در این مثال پایانی، ترکیب کامل این فصل را، با یک مرز Suspense و یک مرز Error Boundary مستقل، یک‌بار دیگر و در کنار هم می‌بینیم.

ترکیب نهایی Suspense و Error Boundary
import { Component, Suspense, lazy } from "react";

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  render() {
    if (this.state.hasError) return this.props.fallback;
    return this.props.children;
  }
}

const Widget = lazy(() => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ default: () => <p>ویجت نهایی آماده شد.</p> });
    }, 900);
  });
});

function App() {
  return (
    <ErrorBoundary fallback={<p>این بخش در دسترس نیست.</p>}>
      <Suspense fallback={<p>در حال بارگذاری ویجت...</p>}>
        <Widget />
      </Suspense>
    </ErrorBoundary>
  );
}
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

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