Lazy Loading در React با تابع lazy

در درس قبل با Dynamic Import، یک قابلیت خام جاوااسکریپت، آشنا شدیم. در این درس می‌بینیم React چگونه این قابلیت را به یک ابزار ساده برای تعریف کامپوننت‌های با بارگذاری دیرهنگام تبدیل کرده است: تابع lazy.

مشکل: import() به‌تنهایی یک کامپوننت نمی‌سازد

نتیجه import()، یک Promise است، نه یک کامپوننت آماده رندر. اگر بخواهیم این Promise را مستقیماً در JSX استفاده کنیم، هیچ معنایی ندارد؛ JSX نمی‌داند با یک Promise چه کار کند.

راه‌حل: تبدیل Promise به یک کامپوننت با lazy

jsx
import { lazy } from "react";

const ChartWidget = lazy(() => import("./ChartWidget"));

lazy یک تابع می‌گیرد که باید خودش یک Promise مربوط به یک import برگرداند، و در عوض، یک کامپوننت معمولی خروجی می‌دهد که می‌توان آن را دقیقاً مثل هر کامپوننت دیگری در JSX استفاده کرد:

jsx
function Dashboard() {
  return (
    <div>
      <Header />
      <ChartWidget />
    </div>
  );
}

نکته مهم: فایل موردنظر باید export default داشته باشد

lazy انتظار دارد خروجی پیش‌فرض (export default) همان فایل، خودِ کامپوننت باشد؛ همان‌طور که در درس قبل دیدیم، این مقدار از module.default خوانده می‌شود، پشت صحنه و به‌طور خودکار توسط React.

jsx
// ChartWidget.js
export default function ChartWidget() {
  return <p>نمودار سنگین اینجا رندر می‌شود</p>;
}

چه زمانی دانلود واقعی اتفاق می‌افتد

برخلاف Static Import که کد از همان ابتدای بارگذاری برنامه حاضر است، کد ChartWidget فقط زمانی دانلود می‌شود که React برای اولین بار تلاش کند آن را Render کند؛ یعنی لحظه‌ای که <ChartWidget /> واقعاً در درخت ظاهر می‌شود، نه زمانی که فایل تعریف const ChartWidget = lazy(...) اجرا می‌شود.

مشکل باقی‌مانده: این فاصله زمانی باید چیزی نمایش دهد

چون دانلود فایل زمان می‌برد (حتی اگر کوتاه)، بین لحظه‌ای که React تلاش می‌کند ChartWidget را Render کند و لحظه‌ای که فایل واقعاً می‌رسد، باید چیزی روی صفحه باشد. اگر این فاصله مدیریت نشود، React با خطا مواجه می‌شود. مدیریت دقیق همین فاصله، با کامپوننت Suspense، موضوع کامل درس پنجم همین فصل است؛ فعلاً کافی است بدانیم چنین ابزاری لازم است.

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

lazy فقط برای Function Componentهای پیش‌فرض کار می‌کند

یک محدودیت فنی مهم: کامپوننتی که با lazy بارگذاری می‌شود، باید حتماً با export default صادر شده باشد؛ صادرات نام‌دار (export function ChartWidget) به‌تنهایی با lazy سازگار نیست، مگر این‌که در یک لایه میانی، به یک export پیش‌فرض تبدیل شود.

چرا نباید همه کامپوننت‌ها را lazy کرد

درست مثل هر بهینه‌سازی دیگری که در فصل سیزدهم دیدیم، lazy هم رایگان نیست؛ هر کامپوننت lazy‌شده، یک درخواست شبکه جداگانه در لحظه نیاز اضافه می‌کند. برای کامپوننت‌های کوچک و سبک که تقریباً همیشه بلافاصله لازم می‌شوند، این تقسیم‌بندی، فقط تأخیر و پیچیدگی اضافه می‌کند، بدون فایده واقعی.

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

استفاده پایه از lazy (بدون Suspense هنوز)
import { useState, lazy, Suspense } from "react";

const HeavyWidget = lazy(() => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        default: function HeavyWidget() {
          return <p>این ویجت دیر بارگذاری‌شده است.</p>;
        },
      });
    }, 1200);
  });
});

function App() {
  const [show, setShow] = useState(false);

  return (
    <div>
      <button onClick={() => setShow(true)}>نمایش ویجت سنگین</button>
      {show && (
        <Suspense fallback={<p>در حال دانلود ویجت...</p>}>
          <HeavyWidget />
        </Suspense>
      )}
    </div>
  );
}
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

تابع lazy، یک تابع برگرداننده Promise مثل () => import("./X") را به یک کامپوننت معمولی تبدیل می‌کند که React فقط در لحظه نیاز واقعی، کد آن را دانلود می‌کند. کامپوننت موردنظر باید با export default صادر شده باشد، و چون این بارگذاری غیرهمزمان است، باید همراه با Suspense استفاده شود. در درس بعدی، به Code Splitting، یعنی دید کلی‌تر این تقسیم‌بندی در سطح کل برنامه، می‌پردازیم.