در جلسه ی قبل با مبحث lazy loading یا code splitting در کامپوننت های react آشنا شدیم و مزیت های این روش را برایتان توضیح دادیم. نکته ی مهم این بود که چنین قابلیتی در برنامه های بسیار بزرگ کاربرد دارد و در برنامه هایی که کوچک باشند فقط تعداد درخواست های ارسالی به سمت سرور را افزایش می دهد. شاید روش جلسه ی قبل برای شما ناخوشایند بوده باشد. بنابراین می خواهم ویژگی جدیدی به نام React Suspense را به شما معرفی کنم. React Suspense در نسخه ی 16.6 به بالا اضافه شده است بنابراین در نسخه های قبل تر از 16.6 نمی توانید از این قابلیت استفاده کنید.
نکته: اگر نمی دانید از چه نسخه ای از React استفاده می کنید باید فایل package.json را باز کرده و در قسمت dependency ها کتابخانه ی react را پیدا کنید:
نکته: در صورتی که نسخه ی کتابخانه ی react شما قدیمی است و می خواهید آن را به روز رسانی کنید باید دستور npm upgrade را در ترمینال خود وارد کنید. این دستور تمامی dependency (وابستگی) های شما را به روز رسانی می کند.
من برای استفاده از React Suspense از یک پروژه ی جدید استفاده می کنم اما برای راحتی شما این پروژه را برایتان قرار می دهم تا دانلود کنید (دانلود پروژه). شما باید این پروژه را دانلود کرده و extract کنید (از حالت فشرده خارج کنید). در مرحله ی بعد ترمینال خود را درون پوشه ی routing--react-suspense-start باز کرده و دستور npm install را اجرا کنید. پس از چند دقیقه صبر فرآیند دانلود و نصب تمام شده و با اجرای دستور npm start می توانید پروژه را در مرورگر خود باز کنید.
زمانی که پروژه را در مرورگر خود باز کنید متوجه تغییرات ظاهری آن خواهید شد که موضوع بحث ما نیست. با استفاده از این قابلیت جدید کدهای هر کامپوننت تنها زمانی دانلود می شوند که واقعا به آن ها نیاز داشته باشیم. اگر به فایل App.js در پروژه بروید، کد زیر را پیدا خواهید کرد:
class App extends Component { render() { return ( <BrowserRouter> <React.Fragment> <nav> <NavLink to="/user">User Page</NavLink> | <NavLink to="/posts">Posts Page</NavLink> </nav> <Route path="/" component={Welcome} exact /> <Route path="/user" component={User} /> <Route path="/posts" component={Posts} /> </React.Fragment> </BrowserRouter> ); } }
احتمالا برایتان جای سوال است که React.Fragment چیست؟ Fragment ها قابلیت جدیدی هستند که در نسخه ی 16.2 کتابخانه ی React اضافه شده اند و دقیقا همان کار کامپوننت Aux خودمان را برای ما انجام می دهند؛ یک عنصر ریشه ای است که در DOM هیچ چیزی اضافه نمی کند.
همچنین در این پروژه 3 صفحه ی جداگانه داریم:
سه Route مرتبط با آن ها نیز در فایل App.js قابل مشاهده هستند:
<Route path="/" component={Welcome} exact /> <Route path="/user" component={User} /> <Route path="/posts" component={Posts} />
این صفحات فقط دارای یک تگ <h1> هستند و محتوای دیگری درون خود ندارند اما ما می خواهیم فرض کنیم که محتوای زیادی در این صفحات وجود دارد. بنابراین بهتر است کاری کنیم که صفحات user و posts فقط زمانی بارگذاری و دانلود شوند که کاربر روی آن ها کلیک می کند. در حال حاضر کامپوننت posts به صورت زیر وارد پروژه شده است:
import Posts from './containers/Posts';
بنابراین همیشه به صورت خودکار دانلود خواهد شد. ما می توانیم این دستور را به شکل زیر بنویسیم:
const Posts = React.lazy(() => import('./containers/Posts'));
به زبان ساده یک متغیر به نام Posts (یا هر نام دلخواه دیگری) ساخته ایم و مقدار آن را برابر با فراخوانی متد lazy از کتابخانه ی react قرار داده ایم. این متد یک تابع را به عنوان پارامتر می گیرد که در نهایت یک دستور ()import را return می کند. این دستور ()import شامل آدرس فایل مورد نظر ما خواهد بود. در حال حاضر نحوه ی نمایش این کامپوننت به شکل زیر است:
<Route path="/posts" component={Posts} />
این حالت برای react قابل قبول نیست بنابراین باید آن را تغییر دهیم. برای این کار می توان گفت:
<Route path="/posts" render={()=> } />
در اینجا یک arrow function داریم که قرار است چیزی را return کند. قبل از آنکه اینجا چیزی بنویسیم باید کامپوننتی به نام Suspense را در پروژه وارد کنیم:
import React, { Component, Suspense } from 'react';
حالا در arrow function خود می گوییم:
<Route path="/posts" render={() => ( <Suspense> <Posts /> </Suspense> )} />
یعنی باید Suspense را در پروژه بگذاریم و سپس کامپوننت مورد نظر خودمان (همان Posts) را بین تگ های آغازین و پایانی آن قرار بدهیم. البته هنوز یک قدم باقی مانده است:
<Route path="/posts" render={() => ( <Suspense fallback={<div>Loading...</div>}> <Posts /> </Suspense> )} />
حالا اگر بارگذاری کدها طول بکشد، react این div را به همراه متن داخلش به کاربر نمایش خواهد داد. البته شما می توانید به جای div از یک spinner یا هر علامت گرافیکی دیگری نیز استفاده کنید.
حالا می توانید مثل جلسه ی قبل به سربرگ network در مرورگر خود (قسمت dev tools) بروید و پس از لود شدن صفحه، روی لینک Posts Page کلیک کنید. به محض انجام این کار مشاهده خواهید کرد که یک فایل جدید در network دانلود می شود که همان کامپوننت Posts ما است.
حالا بگذارید یک حالت دیگر را نیز در نظر بگیریم. تمام کدهای داخل تابع Return را مثل من کامنت کنید:
// <BrowserRouter> // <React.Fragment> // <nav> // <NavLink to="/user">User Page</NavLink> | // <NavLink to="/posts">Posts Page</NavLink> // </nav> // <Route path="/" component={Welcome} exact /> // <Route path="/user" component={User} /> // <Route // path="/posts" // render={() => ( // <Suspense fallback={<div>Loading...</div>}> // <Posts /> // </Suspense> // )} // /> // </React.Fragment> // </BrowserRouter>
سپس فرض می کنیم که فقط یک دکمه به شکل زیر داشته باشیم:
return ( <button>Toggle Mode</button> // <BrowserRouter> // بقیه ی کد ها
حالا یک تابع جدید به همراه یک state به شکل زیر تعریف می کنیم (خارج از تابع ()render):
state = { showPosts: false }; modeHandler = () => { this.setState(prevState => { return { showPosts: !prevState.showPosts }; }); };
واضح است که این متد حالت قبلی State را می گیرد (prevState که قبلا در موردش صحبت کرده بودیم) و آن را برعکس می کند، مثلا اگر true باشد false و اگر false باشد آن را true می کند. حالا این متد را به دکمه ی خودمان متصل می کنیم:
<button onClick={this.modeHandler}>Toggle Mode</button>
و در آخر چک می کنیم که اگر state.showPosts برابر true باشد کامپوننت Posts ها را نمایش بدهیم:
render() { return ( <React.Fragment> <button onClick={this.modeHandler}>Toggle Mode</button> {this.state.showPosts ? ( <Suspense fallback={<div>Loading...</div>}> <Posts /> </Suspense> ) : ( <User /> )} </React.Fragment> // بقیه ی کد ها //
شاید در نگاه اول کد بالا خیلی شلوغ به نظر برسد اما بگذارید برایتان توضیح بدهم تا متوجه آسانی آن بشوید. من می خواهم با کلیک روی دکمه (button) تابع modeHandler اجرا شود تا مقدار state به مقدار true برسد. در خط بعد با یک شرط ساده (از نوع ternary operator) چک کرده ایم که اگر state.showPosts برابر true شد کامپوننت Posts درون <suspense> را نمایش دهیم و در غیر این صورت همان کامپوننت <user> نمایش داده می شود. نباید فقط به خاطر اینکه خط ها را شکسته ام و چند پرانتز اضافه کرده ام فکر کنید کد ما پیچیده شده است. همچنین از آنجا که دو عنصر ریشه ای داریم (یک button و دیگری که یا Posts یا User است) باید از React.Fragment استفاده کنیم تا بتوانیم عنصر های کنار هم را در JSX قرار دهیم.
حالا اگر به مرورگر بروید و روی دکمه کلیک کنید، کامپوننت مورد نظر ما دانلود خواهد شد. شما می توانید این پروسه را در سربرگ network مرورگرتان به سادگی تماشا کنید. نکته ی مهمی که باید از این جلسه به یاد داشته باشید این است که قابلیت lazy load کردن کد ها فقط در پروژه های واقعا بزرگ کاربرد دارد (مثل یک وب سایت پیشرفته) و اگر آن را در پروژه های کوچک و ساده پیاده کنید نه تنها وقت شما را گرفته بلکه ممکن است سرعت برنامه تان را کاهش دهند.
هشدار: تمام این مباحث Routing مربوط به front-end است. سرور همیشه درخواست ها را دریافت می کند بنابراین اگر ما آدرسی مثلا شبیه به myapp/first داشته باشیم با خطای 404 مواجه می شویم! چرا؟ به دلیل اینکه سرور چنین آدرسی را نمی شناسد و این آدرس در react تعریف شده است. راه حل این مشکل این است که همیشه (حتی برای خطاهای 404 نیز) فایل HTML را به کاربر برگردانید.
همچنین به عنوان نکته ی آخر توجه داشته باشید که روش بالا برای حل مشکل خطا های 404 کافی است البته در صورتی که برنامه ی شما در صفحه ی اول (آدرس اصلی /) قرار بگیرد. اگر برنامه ی react ای شما قرار است در آدرسی مثل website.com/my-app قرار بگیرد (به جای اینکه در آدرس اصلی website.com قرار بگیرد) باید این آدرس را به عنوان آدرس پایه ی برنامه تعریف کنید. به طور مثال در پروژه ی ما می توان گفت:
render() { return ( <BrowserRouter basename="/my-app"> <div className="App"> <Blog /> </div> </BrowserRouter> ); }
بنابراین فقط با اضافه کردن basename مشکل را حل کرده ایم. به امید خدا این فصل هم به اتمام رسید و در فصل بعد قابلیت Routing را به پروژه ی همبرگر ساز خودمان اضافه خواهیم کرد.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.