محتوای دینامیک و پیش واکشی در Next.js — آموزش Next.js (بخش سوم)
در این بخش از سری مقالات آموزش Next.js با مفاهیم محتوای دینامیک و پیش واکشی در Next.js آشنا خوهیم شد. بدین ترتیب با پیگیری نمونه اپلیکیشنی که در بخشهای قبل ساختیم، شیوه لینک کردن صفحه اصلی به صفحه بلاگ را فرا میگیریم. بلاگ یکی از کاربردهای عمده Next.js است که با بررسی شیوه افزودن نوشتههای بلاگ در ادامه این سری مقالات آموزشی در مورد آن بیشتر صحبت خواهیم کرد. برای مطالعه بخش قبلی این مجموعه مقالات آموزشی روی لینک زیر کلیک کنید:
نوشتههای بلاگ دارای یک URL دینامیک هستند. برای نمونه نوشتهای با عنوان Hello World میتواند دارای یک URL به صورت /blog/hello-world باشد. نوشتهای با عنوان My second post میتواند مقدار URL به صورت blog/my-/second-post داشته باشد. این محتوا دینامیک است و ممکن است از یک پایگاه داده، فایلهای markdown یا موارد دیگر به دست آمده باشد. Next.js میتواند محتوای دینامیک را بر مبنای یک URL دینامیک عرضه کند. URL دینامیک با ایجاد یک صفحه دینامیک با ساختار [] ساخته میشود.
بدین منظور یک فایل به صورت pages/blog/[id].js میسازیم. این فایل همه URL–های دینامیک را زیر مسیر /blog/ مدیریت میکند. برای نمونه میتوانیم به فایلهایی مانند blog/hello-world/ و blog/my-second-post/ که در بخش فوق بیان شد، اشاره کنیم. در نام فایل، آن مقدار [id] درون براکت به این معنی است که هر چیزی که دینامیک باشد، درون پارامتر id مشخصه کوئری router قرار میگیرد. شاید بیان همه این مفاهیم در کنار هم کمی سردرگمکننده باشد. بنابراین در ادامه آنها را تک به تک توضیح میدهیم. router کتابخانهای است که از سوی Next.js ارائه شده است. آن را از next/router ایمپورت میکنیم:
1import { useRouter } from 'next/router'
زمانی که useRouter را در اختیار داشته باشیم، وهلهای از شیء روتر را با استفاده از دستور زیر ایجاد میکنیم:
1const router = useRouter()
زمانی که شیء روتر را در اختیار داشته باشیم، میتوانیم اطلاعات را از آن استخراج کنیم. به طور خاص میتوانیم با دسترسی به router.query.id بخش دینامیک URL را در فایل [id].js داشته باشیم. دقت کنید که برای نمونه مانند post-[id].js بخش دینامیک نیز میتواند بخشی از URL باشد. در ادامه همه این مفاهیم را در عمل بررسی میکنیم. یک فایل به نام pages/blog/[id].js ایجاد کنید:
1import { useRouter } from 'next/router'
2
3export default () => {
4 const router = useRouter()
5
6 return (
7 <>
8 <h1>Blog post</h1>
9 <p>Post id: {router.query.id}</p>
10 </>
11 )
12}
اکنون اگر به روتر http://localhost:3000/blog/test بروید، با چیزی مانند تصویر زیر مواجه میشوید:
ما میتوانیم از این پارامتر id برای به دست آوردن نوشته از لیستی از نوشتهها استفاده کنیم. برای نمونه این کار میتواند از یک پایگاه داده انجام یابد. برای این که همه چیز سادهتر بماند یک فایل posts.json در پوشه ریشه پروژه اضافه میکنیم:
1{
2 "test": {
3 "title": "test post",
4 "content": "Hey some post content"
5 },
6 "second": {
7 "title": "second post",
8 "content": "Hey this is the second post content"
9 }
10}
اکنون میتوانیم آن را ایمپورت کرده و به دنبال نوشته بر اساس کلید id بگردیم:
1import { useRouter } from 'next/router'
2import posts from '../../posts.json'
3
4export default () => {
5 const router = useRouter()
6
7 const post = posts[router.query.id]
8
9 return (
10 <>
11 <h1>{post.title}</h1>
12 <p>{post.content}</p>
13 </>
14 )
15}
اکنون اگر صفحه را بارگذاری مجدد بکنیم، مطابق انتظار باید چیزی مانند تصویر زیر دیده میشود:
اما در عمل چنین چیزی نمیبینیم، بلکه در کنسول و همچنین در مرورگر با خطایی مواجه میشویم:
دلیل این خطا آن است که در زمان رندرینگ وقتی کامپوننت مقداردهی میشود، دادهها هنوز آماده نیستند. شیوه ارائه دادهها به کامپوننت را در بخش بعدی با استفاده از getInitialProps توضیح میدهیم.
فعلاً پیش از بازگشت دادن JSX یک بررسی ساده به صورت <if (!post) return <p></p اضافه میکنیم:
1import { useRouter } from 'next/router'
2import posts from '../../posts.json'
3
4export default () => {
5 const router = useRouter()
6
7 const post = posts[router.query.id]
8 if (!post) return <p></p>
9
10 return (
11 <>
12 <h1>{post.title}</h1>
13 <p>{post.content}</p>
14 </>
15 )
16}
اکنون همه چیز کار میکند. در ابتدا کامپوننت بدون اطلاعات دینامیک router.query.id رندر میشود. Next.js پس از رندرینگ یک بهروزرسانی با مقدار کوئری آغاز میکند و صفحه اطلاعات صحیح را نمایش میدهد.
اینک اگر سورس صفحه را نگاه کنید، میبینید که یک تگ <p> خالی در HTML وجود دارد:
این مشکل را که موجب شکست پیادهسازی SSR و همچنین تأخیر در زمان بارگذاری صفحه، SEO و اشترک شبکههای اجتماعی میشود به زودی در ادامه مقاله اصلاح خواهیم کرد. مثال بلاگ را با لیست کردن نوشتهها در pages/blog.js تکمیل میکنیم:
1import posts from '../posts.json'
2
3const Blog = () => (
4 <div>
5 <h1>Blog</h1>
6
7 <ul>
8 {Object.entries(posts).map((value, index) => {
9 return <li key={index}>{value[1].title}</li>
10 })}
11 </ul>
12 </div>
13)
14
15export default Blog
میتوانیم با ایمپورت کردن Link از next/link و استفاده از آن درون حلقه posts، آنها را به نوشتههای منفرد بلاگ لینک کنیم:
1import Link from 'next/link'
2import posts from '../posts.json'
3
4const Blog = () => (
5 <div>
6 <h1>Blog</h1>
7
8 <ul>
9 {Object.entries(posts).map((value, index) => {
10 return (
11 <li key={index}>
12 <Link href='/blog/[id]' as={'/blog/' + value[0]}>
13 <a>{value[1].title}</a>
14 </Link>
15 </li>
16 )
17 })}
18 </ul>
19 </div>
20)
21
22export default Blog
پیشواکشی
در بخش قبل گفتیم که Next.js با استفاده از Link میتواند لینکهایی بین 2 صفحه ایجاد کند. زمانی که از آن استفاده کنید Next.js مسیریابی فرانتاند را به شیوهای شفاف برای شما مدیریت میکند. از این رو زمانی که کاربر روی لینکی کلیک کند، صفحه جدید بدون ارسال یک درخواست کلاینت/سرور و چرخه پاسخ، چنان که به طور معمول در صفحههای وب رخ میدهد نمایش میدهد.
همچنین در زمان استفاده از Link، یک کار دیگر هم از سوی Next.js انجام مییابد. به محض این که یک عنصر که درون <Link> قرار دارد، در صفحه ظاهر شود، در صورتی که لینک در وبسایت شما باشد، یعنی یک لینک لوکال به حساب آید، Next.js اقدام به پیشواکشی URL آن میکند. بدین ترتیب اپلیکیشن در دید بیننده بسیار سریع میشود. این رفتار تنها در حالت پروداکشن فعال میشود، یعنی باید در صورت اجرا شدن اپلیکیشن آن را با دستور زیر متوقف کنید:
npm run dev
سپس باندل پروداکشن را با دستور زیر کامپایل کنید:
npm run build
و دوباره با دستور زیر آن را اجرا نمایید:
npm run start
اگر از بخش Network inspector در DevTools استفاده کنید، میبینید که همه لینکهای بخش فوقانی صفحه در زمان بارگذاری صفحه، به محض اجرای رویداد load در صفحه شروع به پیشواکشی میکنند. هر تگ Link دیگر در ویوپورت زمانی که کاربر صفحه را روی آن اسکرول بکند، پیشواکشی میشود. پیشواکشی روی اتصالهای با سرعت بالا (Wifi و 3g+) خودکار است، مگر این که مرورگر یک هدر HTTP به صورت save-data ارسال کند. میتوانید با تعیین prop به نام به صورت false وهلههای Link منفرد را از پیشواکشی منع کنید:
1<Link href="/a-link" prefetch={false}>
2 <a>A link</a>
3</Link>
استفاده از روتر برای تشخیص لینک فعال
یکی از قابلیتهای مهم در زمان کار با لینکها تشخیص این نکته است که URL جاری کدام است و به طور خاص یک کلاس به لینک فعال انتساب میدهد. از این رو میتوانیم آن را نسبت به انواع دیگر به طرز متفاوتی استایلبندی کنیم. برای مثال این کار به طور خاص در هدر مفید است. کامپوننت پیشفرض Link در Next.js که در مسیر next/link ارائه میشود این کار را به صورت خودکار برای ما انجام نمیدهد.
ما میتوانیم یک کامپوننت Link سفارشی برای خودمان بسازیم و آن را در یک فایل به ام Link.js در پوشه کامپوننتها ذخیره کرده و به جای next/link پیشفرض ایمپورت کنیم. در این کامپوننت سفارشی ابتدا ریاکت را از react ،Link را از next/link و قلاب useRuter را از next/router ایمپورت میکنیم. درون کامپوننت بررسی میکنیم که آیا نام مسیر جاری با prop به نام href کامپوننت مطابقت دارد یا نه و اگر چنین باشد، کلاس selected را به فرزندان آن الحاق میکنیم. در نهایت این فرزند را با کلاس بهروز شده با استفاده از ()React.cloneElement بازگشت میدهیم:
1import React from 'react'
2import Link from 'next/link'
3import { useRouter } from 'next/router'
4
5export default ({ href, children }) => {
6 const router = useRouter()
7
8 let className = children.props.className || ''
9 if (router.pathname === href) {
10 className = `${className} selected`
11 }
12
13 return <Link href={href}>{React.cloneElement(children, { className })}</Link>
14}
استفاده از next/router
در بخشهای قبلی با شیوه استفاده از کامپوننت Link برای مدیریت اعلانی مسیریابی در اپلیکیشنهای Next.js آشنا شدیم. مدیریت مسیریابی در JSX کاری ساده است، اما برخی اوقات باید یک تغییر مسیریابی را به صورت برنامهنویسی شده آغاز کنیم. در این حالت، میتوانید مستقیماً به روتر Next.js که از سوی پکیج next/router ارائه میشود دسترسی یابید و متد ()push را فراخوانی کنید. در ادامه مثالی از دسترسی به این روتر را میبینید:
1import { useRouter } from 'next/router'
2
3export default () => {
4 const router = useRouter()
5 //...
6}
زمانی که شیء روتر را با فراخوانی ()useRouter به دست آوریم، میتوانیم از متدهای آن استفاده کنیم. این یک روتر سمت کلاینت است و از این رو متدهای آن باید صرفاً در کد سمت فرانتاند مورد استفاده قرار گیرند. سادهترین روش برای تضمین این نکته، قرار دادن فراخوانیها در قلاب ریاکت ()useEffect یا درون ()componentDidMount در کامپوننتهای با حالت ریاکت است. آنهایی که احتمالاً میخواهید استفاده کنید شامل ()push و ()prefetch هستند. متد ()push امکان آغاز برنامهنویسی شده تغییر URL را در فرانتاند فراهم میسازد:
1router.push('/login')
متد ()prefetch به ما امکان میدهد که یک URL را به صورت برنامهنویسی شده پیشواکشی کنیم و زمانی مفید است که یک تگ Link نداریم که به صورت خودکار پیشواکشی را برای ما مدیریت کند:
1router.prefetch('/login')
به مثال زیر توجه کنید:
1import { useRouter } from 'next/router'
2
3export default () => {
4 const router = useRouter()
5
6 useEffect(() => {
7 router.prefetch('/login')
8 })
9}
همچنین میتوانید از روتر برای گوش دادن به رویدادهای تغییر مسیر استفاده کنید.
ارائه دادهها به کامپوننتها با getInitialProps
در بخش قبلی دیدیم که تولید دینامیک صفحه بلاگ با مشکل مواجه میشود، زیرا کامپوننت نیازمند برخی از دادهها در سمت فرانتاند است. زمانی که تلاش بکنیم دادهها را از فایل JSON بگیریم:
1import { useRouter } from 'next/router'
2import posts from '../../posts.json'
3
4export default () => {
5 const router = useRouter()
6
7 const post = posts[router.query.id]
8
9 return (
10 <>
11 <h1>{post.title}</h1>
12 <p>{post.content}</p>
13 </>
14 )
15}
با خطای زیر مواجه شدیم:
در ادامه روش حل این مشکل و عملیاتی ساختن SSR را برای مسیرهای دینامیک توضیح میدهیم. ما باید برخی props را از طریق تابعی به نام ()getInitialProps که به کامپوننت الحاق میشود، در اختیار کامپوننت قرار دهیم. به این منظور ابتدا نامی برای کامپوننت انتخاب میکنیم:
1const Post = () => {
2 //...
3}
4
5export default Post
سپس تابع را به آن اضافه میکنیم:
1const Post = () => {
2 //...
3}
4
5Post.getInitialProps = () => {
6 //...
7}
8
9export default Post
این تابع شیئی را به همراه چند مشخصه به عنوان آرگومان خود میگیرد. به طور خاص چیزی که به آن علاقهمند هستیم این است که یک شیء query به دست آوریم. این همان شیئی است که قبلاً برای گرفتن id نوشته مورد استفاده قرار دادیم. بنابراین میتوانیم آن را با استفاده از ساختار تخریب شیء زیر به دست آوریم:
1Post.getInitialProps = ({ query }) => {
2 //...
3}
اکنون میتوانیم نوشته را از این تابع بازگردانیم:
1Post.getInitialProps = ({ query }) => {
2 return {
3 post: posts[query.id]
4 }
5}
همچنین میتوانیم ایمپورت useRouter را نیز حذف کنیم و نوشته را از مشخصه props ارسالی به کامپوننت Post به دست آوریم:
1import posts from '../../posts.json'
2
3const Post = props => {
4 return (
5 <div>
6 <h1>{props.post.title}</h1>
7 <p>{props.post.content}</p>
8 </div>
9 )
10}
11
12Post.getInitialProps = ({ query }) => {
13 return {
14 post: posts[query.id]
15 }
16}
17
18export default Post
اکنون دیگر خطایی نخواهد بود و SSR مطابق انتظار کار میکند. این نکته را با دیدن سورس صفحه میتوانید تحقیق کنید:
تابع getInitialProps در سمت سرور اجرا میشود، اما زمانی که با استفاده از کامپوننت Link، مطابق آن چه قبلاً انجام دادیم، به صفحه جدیدی برویم، در سمت کلاینت نیز اجرا خواهد شد.
باید توجه داشته باشیم که getInitialProps در شیء context که دریافت میکند، علاوه بر شیء query مشخصههای دیگر زیر را نیز میگیرد:
- Pathname: بخش path در URL
- asPath: رشتهای که مسیر واقعی را تشکیل میدهد و در مرورگر نمایش مییابد.
در این مورد با فراخوانی http://localhost:3000/blog/test به ترتیب مقادیر زیر به دست میآید:
- /blog/[id]
- /blog/test
در مورد رندرینگ سمت سرور نیز مقادیر زیر به دست میآید:
- req: یک شیء درخواست HTTP.
- res: شیء پاسخ HTTP.
- err: یک شیء خطا.
در صورتی که با Node.js کار کرده باشید اشیای req و res برای شما آشنا خواهند شد. بدین ترتیب به پایان این بخش از سری مقالههای آموزش Next.js میرسیم. در بخش بعدی با شیوه استفاده از CSS و کار با تگها و استایلبندی در اپلیکیشنهای Next.js آشنا خواهیم شد. برای مطالعه بخش بعدی این سری مقالات روی لینک زیر کلیک کنید:
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- مفاهیم مقدماتی و شیوه نصب Next.js — آموزش Next.js (بخش اول)
- هشت ترفند مفید برای توسعه اپلیکیشن های React — راهنمای کاربردی
- طراحی احراز هویت مقدماتی با React — به زبان ساده
==