Dynamic Import: وارد کردن یک ماژول در لحظه نیاز
در فصل پانزدهم، هنگام بحث درباره Route-based Code Splitting، بهطور مختصر با Dynamic Import آشنا شدیم. در این فصل، بهطور مستقل و کامل به این قابلیت میپردازیم؛ چون پایه اصلی هر نوع Code Splitting و Lazy Loading در جاوااسکریپت، همین یک ویژگی است.
یادآوری: import معمولی در بالای فایل
تا اینجا در این دوره، همیشه ماژولها را با import در ابتدای فایل وارد کردهایم:
import { formatPrice } from "./utils";این نوع import، Static Import نام دارد. ویژگی مهم آن این است که در زمان Build (پیش از اجرای برنامه در مرورگر)، پردازش میشود؛ یعنی ابزار بستهبندی (مثل Webpack یا Vite)، از همان ابتدا میداند این ماژول باید در بسته نهایی جاوااسکریپت گنجانده شود.
مشکل: همه ماژولها، همیشه لازم نیستند
اگر تابعی مثل generatePdfReport فقط وقتی کاربر روی یک دکمه خاص کلیک میکند لازم باشد، وارد کردن آن با Static Import، یعنی کد آن تابع (و هر کتابخانهای که به آن وابسته است) از همان ابتدا، برای همه کاربران، حتی کسانی که هرگز آن دکمه را نمیزنند، دانلود میشود.
راهحل: Dynamic Import
Dynamic Import، نحو متفاوتی دارد: بهجای کلمه کلیدی import در بالای فایل، از تابع import() استفاده میشود که یک Promise برمیگرداند:
function handleExportClick() {
import("./pdfGenerator").then((module) => {
module.generatePdfReport();
});
}
کد داخل ./pdfGenerator، دیگر بخشی از بسته اولیه جاوااسکریپت نیست؛ ابزار بستهبندی، آن را در یک فایل جداگانه قرار میدهد که فقط در لحظه فراخوانی import()، از سرور دانلود میشود.
چرا Dynamic Import یک Promise برمیگرداند
چون دانلود یک فایل جدید از شبکه، طبیعتاً یک عملیات غیرهمزمان است؛ دقیقاً همان نوعی از عملیات که در فصل ششم و چهاردهم با Promise و fetch دیدیم. به همین دلیل، import() هم میتواند با async/await نوشته شود:
async function handleExportClick() {
const module = await import("./pdfGenerator");
module.generatePdfReport();
}دسترسی به خروجیهای ماژول
مقداری که Promise برمیگرداند، یک آبجکت است که شامل تمام چیزهایی است که آن فایل export کرده: خروجی پیشفرض آن در module.default قرار دارد، و خروجیهای نامدار، مستقیم با همان نام روی همین آبجکت در دسترساند:
// 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 دیده شود.
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>
);
}جمعبندی
Dynamic Import، با نحو import()، یک Promise برمیگرداند و به مرورگر اجازه میدهد کد یک ماژول را فقط در لحظه نیاز، نه از همان ابتدای بارگذاری برنامه، دانلود کند. این قابلیت، بخشی از خود جاوااسکریپت است و پایه اصلی Lazy Loading و Code Splitting، که در درسهای بعدی این فصل با جزئیات بیشتری در React بررسی میشوند، به شمار میرود.
