Appearance
定義路由:動態與巢狀
上一節用兩筆資料完成第一個路由表。這一節把它擴成真正的書店:同一個詳情元件要顯示不同書籍,詳情裡還有簡介與評論子頁,最後也得接住不存在的網址。
路由表就是對應資料
routes 是一個陣列,每筆 route record 至少描述 path 與 component:
js
export const routes = [
{
path: '/',
component: () => import('../views/BookListView.vue'),
},
]Vue Router 讀到 / 時,便載入 BookListView.vue。這裡先把注意力放在路由結構;() => import() 的 lazy loading 會在第四節完整說明。
動態路由:一條規則接住多本書
如果每一本書都各寫一筆 /books/1、/books/2,路由表會跟資料量一起膨脹。動態參數用冒號開頭,一條 /books/:id 就能接住所有書籍 id:
js
{
path: '/books/:id',
component: () => import('../views/BookDetailView.vue'),
}同一筆 route record 會配對不同 URL,差異收進 params:
| URL | 配對的 path | route.params |
|---|---|---|
/books/1 | /books/:id | { id: '1' } |
/books/2 | /books/:id | { id: '2' } |
/books/vue-router | /books/:id | { id: 'vue-router' } |
在頁面元件裡呼叫 useRoute(),就能從 route.params 取得目前網址的參數:
src/views/BookDetailView.vue:
vue
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
</script>
<template>
<article>
<h1>書籍 {{ route.params.id }}</h1>
</article>
</template>從 /books/1 切到 /books/2 時,兩個網址配到的是同一筆 route record、同一個元件。Vue Router 會重用元件實體,不會重新執行掛載生命週期。先記住這件事;第四節會讓資料隨參數重新取得,而不是依賴 onMounted()。
巢狀路由:詳情裡還有子頁
第四章曾用動態元件做書籍簡介與評論頁籤。如果希望讀者能分享「某本書的評論」網址,就要讓頁籤進入 URL:
/books/1:書籍 1 的簡介/books/1/reviews:書籍 1 的評論
父 route record 用 children 宣告子路由。子路徑 reviews 不以 / 開頭,會接在父路徑後面:
js
{
path: '/books/:id',
component: () => import('../views/BookDetailView.vue'),
children: [
{
path: '',
component: () => import('../views/BookIntroView.vue'),
},
{
path: 'reviews',
component: () => import('../views/BookReviewsView.vue'),
},
],
}父元件除了自己的標題與頁籤,還要提供子層 <RouterView>:
vue
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
</script>
<template>
<article>
<h1>書籍 {{ route.params.id }}</h1>
<nav aria-label="書籍詳情分頁">
<RouterLink :to="`/books/${route.params.id}`">簡介</RouterLink>
<RouterLink :to="`/books/${route.params.id}/reviews`">評論</RouterLink>
</nav>
<RouterView />
</article>
</template>外層 App.vue 的 <RouterView> 放入 BookDetailView;內層的 <RouterView> 再放入 BookIntroView 或 BookReviewsView。和第四章只存在記憶體裡的頁籤相比,路由頁籤多了一個真實的 URL,分享、書籤、上一頁都因此可用。
常見錯誤:子路徑多寫了 /
巢狀路由的子 path 若寫成 /reviews,它會變成根路徑,不再接在 /books/:id 後面:
js
children: [
{ path: '/reviews', component: BookReviewsView }, // ❌ /reviews
{ path: 'reviews', component: BookReviewsView }, // ✅ /books/:id/reviews
]用名稱指向路由
替 route record 加上唯一的 name,呼叫端就不必到處手刻路徑:
js
{
path: '/books/:id',
name: 'book',
component: () => import('../views/BookDetailView.vue'),
}之後即使把路徑改成 /catalog/:id,使用 { name: 'book', params: { id: 1 } } 的連結仍然指向同一筆路由。命名路由會在下一節直接用上。
Redirect 與 404
redirect 適合處理「舊入口要送去哪裡」或「父層要有預設子頁」:
js
{
path: '/catalog',
redirect: { name: 'home' },
}最後一筆 route record 可以用 catch-all 接住前面都沒配到的網址:
js
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('../views/NotFoundView.vue'),
}(.*)* 表示接受任意深度的剩餘路徑。404 是公開頁面;即使之後加入登入守衛,也不該讓「找不到頁面」先被送去登入。
路由表現在已能描述書店的頁面層級。下一節要讓使用者透過 RouterLink 與程式碼在這些路由間移動。
