Dynamic Import: وارد کردن یک ماژول در لحظه نیاز

در فصل پانزدهم، هنگام بحث درباره Route-based Code Splitting، به‌طور مختصر با Dynamic Import آشنا شدیم. در این فصل، به‌طور مستقل و کامل به این قابلیت می‌پردازیم؛ چون پایه اصلی هر نوع Code Splitting و Lazy Loading در جاوااسکریپت، همین یک ویژگی است.

یادآوری: import معمولی در بالای فایل

تا اینجا در این دوره، همیشه ماژول‌ها را با import در ابتدای فایل وارد کرده‌ایم:

javascript
import { formatPrice } from "./utils";

این نوع import، Static Import نام دارد. ویژگی مهم آن این است که در زمان Build (پیش از اجرای برنامه در مرورگر)، پردازش می‌شود؛ یعنی ابزار بسته‌بندی (مثل Webpack یا Vite)، از همان ابتدا می‌داند این ماژول باید در بسته نهایی جاوااسکریپت گنجانده شود.

مشکل: همه ماژول‌ها، همیشه لازم نیستند

اگر تابعی مثل generatePdfReport فقط وقتی کاربر روی یک دکمه خاص کلیک می‌کند لازم باشد، وارد کردن آن با Static Import، یعنی کد آن تابع (و هر کتابخانه‌ای که به آن وابسته است) از همان ابتدا، برای همه کاربران، حتی کسانی که هرگز آن دکمه را نمی‌زنند، دانلود می‌شود.

راه‌حل: Dynamic Import

Dynamic Import، نحو متفاوتی دارد: به‌جای کلمه کلیدی import در بالای فایل، از تابع import() استفاده می‌شود که یک Promise برمی‌گرداند:

javascript
function handleExportClick() {
  import("./pdfGenerator").then((module) => {
    module.generatePdfReport();
  });
}

کد داخل ./pdfGenerator، دیگر بخشی از بسته اولیه جاوااسکریپت نیست؛ ابزار بسته‌بندی، آن را در یک فایل جداگانه قرار می‌دهد که فقط در لحظه فراخوانی import()، از سرور دانلود می‌شود.

چرا Dynamic Import یک Promise برمی‌گرداند

چون دانلود یک فایل جدید از شبکه، طبیعتاً یک عملیات غیرهمزمان است؛ دقیقاً همان نوعی از عملیات که در فصل ششم و چهاردهم با Promise و fetch دیدیم. به همین دلیل، import() هم می‌تواند با async/await نوشته شود:

javascript
async function handleExportClick() {
  const module = await import("./pdfGenerator");
  module.generatePdfReport();
}

دسترسی به خروجی‌های ماژول

مقداری که Promise برمی‌گرداند، یک آبجکت است که شامل تمام چیزهایی است که آن فایل export کرده: خروجی پیش‌فرض آن در module.default قرار دارد، و خروجی‌های نام‌دار، مستقیم با همان نام روی همین آبجکت در دسترس‌اند:

javascript
// pdfGenerator.js
export default function generatePdfReport() { /* ... */ }
export const PDF_VERSION = "2.0";

// استفاده
const module = await import("./pdfGenerator");
module.default(); // همان generatePdfReport
console.log(module.PDF_VERSION);

این یک ویژگی جاوااسکریپت است، نه React

نکته مهمی که باید روشن باشد: import() بخشی از استاندارد خود زبان جاوااسکریپت است، دقیقاً مثل Promise یا fetch که در فصل‌های قبل دیدیم؛ هیچ ارتباط مستقیمی به React ندارد. React فقط، همان‌طور که در درس بعدی می‌بینیم، یک ابزار به نام lazy ارائه می‌دهد که این قابلیت زبان را به‌شکل راحت‌تری با کامپوننت‌ها ترکیب می‌کند.

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

چون در این محیط، import از یک فایل واقعی دیگر ممکن نیست، در این مثال یک تابع با همان امضای Promise، به‌جای import واقعی، شبیه‌سازی شده تا همان جریان async دیده شود.

شبیه‌سازی Dynamic Import با یک Promise
import { useState } from "react";

// این تابع، جای import("./heavyMath") را می‌گیرد
function loadHeavyMathModule() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        default: function sumRange(n) {
          let total = 0;
          for (let i = 1; i <= n; i++) total += i;
          return total;
        },
      });
    }, 800);
  });
}

function App() {
  const [result, setResult] = useState(null);
  const [loading, setLoading] = useState(false);

  async function handleClick() {
    setLoading(true);
    const module = await loadHeavyMathModule();
    setResult(module.default(100));
    setLoading(false);
  }

  return (
    <div>
      <button onClick={handleClick}>محاسبه با بارگذاری دیرهنگام ماژول</button>
      {loading && <p>در حال بارگذاری ماژول...</p>}
      {result !== null && <p>نتیجه: {result}</p>}
    </div>
  );
}
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

Dynamic Import، با نحو import()، یک Promise برمی‌گرداند و به مرورگر اجازه می‌دهد کد یک ماژول را فقط در لحظه نیاز، نه از همان ابتدای بارگذاری برنامه، دانلود کند. این قابلیت، بخشی از خود جاوااسکریپت است و پایه اصلی Lazy Loading و Code Splitting، که در درس‌های بعدی این فصل با جزئیات بیشتری در React بررسی می‌شوند، به شمار می‌رود.