如果有任何問題或建議,歡迎隨時聯繫我:
在前面的章節中,我們學會了如何建立組件、讓它們溝通、甚至用 Composable
函式來封裝和複用邏輯。我們的應用程式已經具備了強大的「內在」。但一個現代化的網站,很少只有一個頁面。
我們需要首頁、關於我們、產品列表、使用者個人資料頁...等等。如果每次切換頁面都要向伺服器重新請求、載入整個網頁,那體驗就太糟糕了。
這就是 客戶端路由 (Client-Side Routing) 發揮作用的地方。今天,我們將學習 Vue 的官方路由管理器:Vue Router,它能讓我們打造出如絲般滑順的 單頁式應用 (Single-Page Application, SPA)。
傳統網站是「多頁式應用 (MPA)」,你點一個連結,瀏覽器就向伺服器請求一個全新的 HTML 檔案,然後載入、渲染,整個頁面會「刷新」。
而 SPA 則更像一個桌面應用程式。它只在初次載入時,從伺服器獲取一個主要的 HTML 檔案及所有必要的資源 (JS/CSS)。之後,當使用者點擊導覽連結時,它不會重新載入整個頁面,而是透過 JavaScript 動態地攔截導覽,並在不刷新的情況下,切換顯示對應的「視圖 (View)」或「組件」。
這帶來的好處是:
雖然我們說有核心三劍客,但真正的威力都藏在細節裡。讓我們先深入 router/index.js
,看看這份「導航地圖」還能做些什麼。
createRouter
的核心選項createRouter
是我們建立路由實例的地方,它接收一個選項物件。
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
const router = createRouter({
// history: 控制路由模式,最常用的是兩種
// 1. createWebHistory(): HTML5 模式,URL 好看,但需要後端配合處理 404
// 2. createWebHashHistory(): Hash 模式,URL 裡有個 #,對 SEO 不友善,但不需要後端配置
history: createWebHistory(),
// routes: 路由規則的陣列,這是核心中的核心
routes: [
// ... 路由規則
],
// scrollBehavior: 控制頁面切換時的滾動行為
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
// 如果有儲存的捲動位置(例如按上一頁),就回到那個位置
return savedPosition;
} else {
// 否則,一律滾動到頁面頂部
return { top: 0, behavior: 'smooth' };
}
},
});
routes
陣列中的每一個物件,都是一條路由規則,它也有許多強大的屬性:
{
// path: (必須) URL 路徑,支援動態參數,例如 /users/:id
path: '/user/:id',
// component: (必須) 對應要渲染的組件
component: () => import('../views/User.vue'), // 推薦使用動態載入,優化首頁速度
// name: 路由的唯一名稱,方便在程式中進行導航,例如 router.push({ name: 'User', params: { id: 123 }})
name: 'User',
// redirect: 重新導向,可以是一個路徑字串,或是一個函式
// redirect: '/team/:id',
redirect: to => ({ path: '/team', query: { id: to.params.id } }),
// alias: 路由的「別名」,/profile 會和 /user/:id 渲染同一個組件,但 URL 保持 /profile
alias: '/profile/:id',
// props: 將路由參數解耦,直接當作 props 傳入組件,讓組件更獨立
// true -> 將 route.params 直接當作 props 傳入
// 物件 -> 傳入靜態的 props
// 函式 -> 自訂傳入的 props
props: route => ({ userId: route.params.id, query: route.query.q }),
// meta: 元資料,可以放任何自訂的資訊,最常用來做權限驗證
meta: {
requiresAuth: true, // 這個頁面需要登入
title: '使用者中心'
},
// children: 巢狀路由,用來實現複雜的頁面佈局
children: [
{
// 當 URL 是 /user/:id/profile 時,UserProfile 會被渲染在 User 組件的 <router-view> 中
path: 'profile',
component: UserProfile,
},
{
path: 'posts',
component: UserPosts,
},
],
}
小提示:在
children
陣列中的path
不可以用/
開頭,它會被自動接在父層路徑的後面。
Vue Router 提供了一套「導航守衛」機制,它就像是每個路由路口上的保全人員,讓你在使用者進入、離開或更新路由時,可以執行一些邏輯。這對於權限檢查、資料預先載入、或防止使用者意外離開未儲存的頁面等場景,至關重要。
導航守衛有三個關鍵參數:to
, from
, next
。
to
: 即將要進入的目標路由物件。from
: 當前正要離開的路由物件。next
: 一個必須被呼叫的函式,用來解析這個守衛。
next()
: 正常放行,進入下一個守衛。next(false)
: 中斷當前的導航。next('/')
或 next({ name: 'Login' })
: 重導向到一個不同的位址。router.beforeEach
)這是最常用的一個守衛,它在每一次路由切換前都會被觸發。最適合用來做全站的登入權限驗證。
router/index.js
router.beforeEach((to, from, next) => {
const isLoggedIn = !!localStorage.getItem('token'); // 簡易的登入判斷
// 檢查目標路由是否需要登入
if (to.meta.requiresAuth && !isLoggedIn) {
// 如果需要登入但使用者未登入,就導向登入頁
next({ name: 'Login', query: { redirect: to.fullPath } });
} else {
// 否則,正常放行
next();
}
});
有時候,你只想在特定組件中處理路由邏輯。例如,當使用者正在編輯表單但尚未儲存時,阻止他離開。
EditForm.vue
<script setup>
import { ref } from 'vue';
import { onBeforeRouteLeave } from 'vue-router';
const isFormDirty = ref(true); // 假設表單已被修改
// 在使用者嘗試離開此路由時觸發
onBeforeRouteLeave((to, from) => {
if (isFormDirty.value) {
const answer = window.confirm('您有未儲存的變更,確定要離開嗎?');
if (!answer) {
return false; // 如果使用者選擇「取消」,則中斷導航
}
}
});
</script>
除了 onBeforeRouteLeave
,還有 onBeforeRouteUpdate
,它在當前路由改變,但該組件仍然被複用時呼叫(例如,從 /users/1
導航到 /users/2
)。
思考一:設計部落格路由
請試著設計一個簡單部落格的路由表。它需要包含:
/
)。/posts/:postId
)。/about
)。思考二:useRoute
vs useRouter
Vue Router 提供了兩個很像的 Composable 函式:useRoute()
和 useRouter()
。它們有什麼不同?分別應該在什麼情境下使用? (提示:一個是唯讀的資訊,一個是可執行的方法)
今天我們學會了如何使用 Vue Router 來為我們的應用程式增加「導航」功能,正式從單一組件的練習,邁向了多視圖的「單頁式應用」。
routes
:定義 URL 路徑與組件對應關係的「導航地圖」。<router-link>
:取代 <a>
標籤,用來產生無刷新的導航連結。<router-view>
:用來顯示當前路由對應組件的「渲染出口」。:param
的形式來匹配一類模式的 URL。children
屬性,實現複雜的頁面佈局。我們的應用程式現在擁有了不同的「房間」(頁面),但一個新的問題出現了:如果住在不同房間的人需要共享一個物品(資料),該怎麼辦?明天,我們將學習新一代的 Vue 狀態管理器 Pinia,來解決跨組件的資料共享問題!