เขียนเว็บด้วย Next.js + TypeScript ตอนที่ 2 - ว่าด้วยเรื่อง Routing และ Dynamic Routes
สวัสดีครับ มาต่อกันที่ ตอนที่ 2 วันนี้จะเป็นเรื่องของ Routing และ Dynamic Routes ครับ วันนี้จะเป็นเนื้อหาเพิ่มเติมของตอนที่ 1 เกี่ยวกับการสร้างหน้าเพจ และ Route

อย่างที่ยกตัวอย่างคร่าวๆ ในตอนที่ 1 ไปเรื่องของการสร้าง Pages ใน Next.js
- เมื่อเราเพิ่มไฟล์ในโฟลเดอร์
pages
มันก็จะเป็น routes ให้อัตโนมัติ - ก่อนหน้านี้เราสร้างหน้า About ก็คือเพิ่มไฟล์
pages/about.tsx
จากนั้น routes ก็จะถูก generate เป็น/about
ให้
Index Routes
เราสามารถกำหนดชื่อไฟล์เป็น index.tsx
ก็ได้ หรือมีโฟลเดอร์หลายชั้นก็ได้ ตัว routes จะถูกแปลงเป็นแบบตัวอย่างนี้
pages/index.tsx
→/
pages/blog/index.tsx
→/blog
pages/about/index.tsx
/about
Nested Routes
จะทำ routing หลายๆชั้นก็ทำได้ เช่น
pages/blog/first-post.tsx
→/blog/first-post
pages/dashboard/settings/username.tsx
→/dashboard/settings/username
Dynamic Routes
รองรับการทำ Dynamic Routes สมมติเราทำเว็บมีสินค้าในระบบ 1000 ชิ้น ต้องมาเขียนไฟล์ 1000 ไฟล์ ไม่ไหวแน่ๆ ตัว Next ก็รองรับ dynamic route แบบนี้
- ตั้งชื่อไฟล์หรือโฟลเดอร์ด้วย
[]
เช่น[id].tsx
หรือ โฟลเดอร์[users]
pages/products/[id].tsx
→/products/:id
- เช่น/products/1
,/products/5
pages/post/[...all].tsx
→/post/*
(/post/2020/id/title
)
ข้อดีคือ เราใช้แค่ไฟล์เดียว ที่ match กับ :id
ที่เราต้องการ
เรื่องของ Dynamic Routes
เราจะมาลงลึกในส่วน Dynamic Routes กันครับ เริ่มจาก เราจะกำหนดให้เว็บเรามี product เป็น url แบบนี้
/products/:id
เช่น/products/1
,/products/2
สิ่งที่เราต้องทำ ก็คือ สร้างไฟล์ [id].tsx
ขึ้นมา ในโฟลเดอร์ pages/products
const ProductDetail = () => {
return (
<h1>This is Product Detail Page</h1>
)
}
export default ProductDetail
เราสามารถ get params จาก [id]
ได้ด้วยการใช้ next/router
import { useRouter } from 'next/router'
const ProductDetail = () => {
const router = useRouter()
const { id } = router.query
return (
<h1>This is Product {id}</h1>
)
}
export default ProductDetail
จากโค๊ดด้านบน ตัว Router จะ get object จาก params ที่เราตั้งชื่อไว้แบบเดียวกับชื่อไฟล์ นอกจากนี้ ตัว Router ยัง get query parameters ด้วยนะครับ ตัวอย่าง pages แบบต่างๆ จะได้ค่า query
ยังไงบ้าง?
pages/products/[id]/[comment].tsx
->pages/products/1/my-comment
{ "id": "1", "comment": "my-comment" }
/pages/products/100?sku=test
{ "id": "100", "sku": "test }
ทีนี้เราลองทดสอบด้วยการ start dev server ขึ้นมา แล้วลองเข้าเว็บ ด้วย url ต่างๆ จะเห็นค่า product id ที่แตกต่างกัน
npm run dev
- http://localhost:3000/products/1
- http://localhost:3000/products/50
Catch all routes
ตัว Dynamic routes เรายังสามารถ ดักจับ path ทั้งหมด ได้ด้วยการใช้ 3จุด (...
) ข้างใน []
ตัวอย่างเช่น
pages/posts/[...all].js
จะ match ทั้ง/posts/1
,/posts/1/2/3
,/posts/1/comments
อะไรก็แล้วแต่ ที่ตามหลัง/posts
จะเข้าเงื่อนไขทั้งหมด
// เช่น path /posts/1/comments/1000
const { all } = router.query
// all: ['1', 'comments', '1000']
การ Link กันระหว่างหน้า
ในตอนที่ 1 พูดไปแล้ว เรื่องการใช้ next/link
ตัวอย่างก่อนหน้านี้
<Link href="/products/1">Go to Product 1</Link>
เราอยากใส่ Link อะไรก็แค่เปลี่ยน href
ใช่มั้ยครับ หรือถ้าจะส่ง query string ไปด้วย ก็เป็น href="/products/1?color=red&size=m
ก็ทำได้
หรือ เราจะส่งแบบ URL Object แบบนี้ก็ได้ ไม่ต้องนำ query มาต่อ url ให้วุ่นวาย
<Link
href={{
pathname: '/products/[id]',
query: { id: 1 },
}}
>
Product #1
</Link>
<Link
href={{
pathname: '/products/[id]',
query: { id: 1, color: "red", size: "m" },
}}
>
Product #1
</Link>
ลงมือโค๊ดดีกว่า
ลงมือทำดีกว่า เพื่อให้เห็นภาพจริงๆ เริ่มจาก ลองทำหน้าขึ้นมา หน้า 1 เป็นหน้า Product List ครับ ผมตั้งชื่อว่า pages/products/index.tsx
มีข้อมูล mockProducts
ใส่ไปด้วย เป็น ดังนี้
import Link from 'next/link'
const mockProducts = [
{
id: 1,
title: 'Super cool T-Shirt',
size: 'm',
color: 'red',
},
{
id: 2,
title: 'Hello T-Shirt',
size: 'l',
color: 'black',
},
{
id: 3,
title: 'Ahoy Hoodie',
size: 'l',
color: 'black',
},
]
interface Product {
id: number
title: string
size: string
color: string
}
export default function Products() {
return (
<div>
<h2>My Products</h2>
<ul>
{mockProducts.map(({ id, title, size, color }: Product) => {
return (
<li key={id}>
<Link
href={{
pathname: '/products/[id]',
query: { id, size, color },
}}
>
{title}
</Link>
</li>
)
})}
</ul>
</div>
)
}
จากนั้นสร้างอีกไฟล์ สำหรับ Product Detail ชื่อ pages/products/[id].tsx
ตอนนี้เราจะมี 2 ไฟล์ ในโฟลเดอร์ pages/products
แล้วนะครับ คือ
index.tsx
- หน้า Products รวม จะเป็น route →/products
[id].tsx
- หน้า Detail จะเป็น route →/products/:id
ไฟล์ products/[id].tsx
จะมีข้อมูลดังนี้ แค่แสดงข้อมูล id จาก router
import { useRouter } from 'next/router'
import Link from 'next/link'
const ProductDetail = () => {
const router = useRouter()
const { id, color, size } = router.query
return (
<div>
<h1>This is Product {id}</h1>
<p>Color : {color}</p>
<p>Size : {size}</p>
<Link href="/products">List Products</Link>
</div>
)
}
export default ProductDetail
ทดลอง start dev server ดูผลลัพธ์
npm run dev
🎉 Done!
จบไปแล้วครับสำหรับตอนที่ 2 ไม่ได้เน้นหน้าตา UI นะครับ เน้นให้เข้าใจการทำงานของ Routes และ Dynamic Routes รวมถึงการอ่านค่าจาก router params เป็นต้น