Route-based Code Splitting
تا اینجا در این دوره، فرض کردیم کل کد جاوااسکریپت یک برنامه، در یک فایل واحد به مرورگر کاربر فرستاده میشود. اما با بزرگتر شدن برنامه و افزایش تعداد صفحات، این فایل واحد هم بزرگتر میشود؛ حتی اگر کاربر فقط صفحه خانه را ببیند، مجبور است کد مربوط به داشبورد، تنظیمات و دهها صفحه دیگر را هم دانلود کند. Route-based Code Splitting، راهحل رایج این مشکل است.
مشکل: یک بسته بزرگ برای همه، حتی اگر لازم نباشد
هرچه برنامه رشد کند، حجم فایل جاوااسکریپت اولیه هم بزرگتر میشود. این یعنی حتی کاربری که فقط صفحه خانه را باز میکند، باید منتظر دانلود و اجرای کدی بماند که مربوط به صفحاتی است که شاید هرگز به آنها سر نزند.
ایده Code Splitting: تقسیم بسته به چند فایل کوچکتر
Code Splitting یعنی بهجای یک فایل جاوااسکریپت غولپیکر، کد را به چند فایل کوچکتر تقسیم کنیم و هرکدام را فقط زمانی بارگذاری کنیم که واقعاً لازم است. در ترکیب با Routing، رایجترین مرز برای این تقسیم، خود صفحات (Routeها) هستند: کد هر صفحه، در یک فایل جدا قرار میگیرد و فقط وقتی کاربر به آن مسیر میرود، همان فایل دانلود میشود.
ابزار پایه: import پویا
این قابلیت بر پایه ویژگی جاوااسکریپتی به نام Dynamic Import بنا شده که اجازه میدهد یک ماژول، بهجای وارد شدن در بالای فایل، در لحظه نیاز و بهصورت غیرهمزمان بارگذاری شود:
import("./ProductsPage").then((module) => {
// module.default همان کامپوننت است
});جزئیات کاملتر Dynamic Import و Lazy Loading، موضوع فصل بیستم این دوره است؛ در اینجا فقط بهاندازهای که برای فهم کاربرد آن در Routing لازم است، به آن میپردازیم.
استفاده در تعریف Routeها
React، تابع lazy را ارائه میدهد که یک import پویا را به یک کامپوننت معمولی تبدیل میکند:
import { lazy } from "react";
const ProductsPage = lazy(() => import("./ProductsPage"));
const SettingsPage = lazy(() => import("./SettingsPage"));
const router = createBrowserRouter([
{ path: "/", element: <HomePage /> },
{ path: "/products", element: <ProductsPage /> },
{ path: "/settings", element: <SettingsPage /> },
]);
با این تغییر، کد ProductsPage و SettingsPage دیگر بخشی از بسته اولیه نیستند؛ هرکدام فقط وقتی کاربر به مسیر مربوطه برود، بهصورت جداگانه دانلود میشوند.
نیاز به Suspense
چون بارگذاری این کامپوننتها حالا غیرهمزمان است، React باید بداند در فاصله زمانی دانلود فایل، چه چیزی نمایش دهد. این کار با پیچیدن بخش موردنظر در Suspense انجام میشود:
import { Suspense } from "react";
function App() {
return (
<Suspense fallback={<p>در حال بارگذاری صفحه...</p>}>
<RouterProvider router={router} />
</Suspense>
);
}
Suspense ابزاری عمومیتر است که با جزئیات کامل در فصل بیستویکم بررسی میشود؛ همین جا کافی است بدانیم که fallback آن، دقیقاً همان چیزی است که تا پایان دانلود کامپوننت Lazy، بهجای آن نمایش داده میشود.
کدام مسیرها را Split کنیم
معمولاً منطقیترین کار، اعمال lazy روی صفحاتی است که بهاندازه کافی سنگیناند یا بهندرت بازدید میشوند (مثل یک صفحه تنظیمات پیچیده)، نه صفحاتی که تقریباً همیشه بلافاصله پس از ورود کاربر لازم میشوند (مثل خود صفحه خانه). Split کردن بیشازحد، هر صفحه را به چند درخواست شبکه کوچک تبدیل میکند که خودش هم هزینه دارد.
مثال قابل اجرا
در این مثال، چون import واقعی از فایل دیگر در این محیط ممکن نیست، تأخیر دانلود با یک Promise شبیهسازی شده تا رفتار Suspense و fallback دیده شود.
import { useState, Suspense, lazy } from "react";
function fakeImportSettingsPage() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
default: function SettingsPage() {
return <p>صفحه تنظیمات (دیر بارگذاریشده)</p>;
},
});
}, 1500);
});
}
const SettingsPage = lazy(fakeImportSettingsPage);
function App() {
const [showSettings, setShowSettings] = useState(false);
return (
<div>
<button onClick={() => setShowSettings(true)}>رفتن به تنظیمات</button>
{showSettings && (
<Suspense fallback={<p>در حال بارگذاری صفحه تنظیمات...</p>}>
<SettingsPage />
</Suspense>
)}
</div>
);
}جمعبندی
Route-based Code Splitting یعنی تقسیم بسته جاوااسکریپت بر اساس صفحات، با کمک import پویا و تابع lazy، تا کاربر فقط کد همان صفحهای که میبیند را دانلود کند. چون این بارگذاری غیرهمزمان است، باید با Suspense و یک fallback مناسب همراه شود. با این درس، فصل پانزدهم تقریباً به پایان میرسد و در ادامه به آزمون فصل ۱۵ میرویم.
