Vue3 路由系統(Vue Router 4)—— 程式化導航(router.push、router.replace)
簡介
在單頁應用(SPA)中,路由是連結不同畫面與功能的核心。Vue Router 4 為 Vue 3 提供了完整且彈性的路由解決方案,而「程式化導航」則是讓我們在 JavaScript 內部自行控制頁面跳轉的關鍵技巧。
相較於在模板中直接使用 <router-link>,程式化導航允許 根據使用者行為、API 回傳結果或業務邏輯 動態決定要前往的路由。掌握 router.push 與 router.replace,不僅能提升使用者體驗,還能在表單提交、權限驗證、錯誤處理等情境中保持程式碼的可讀性與可維護性。
以下將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你深入了解 Vue Router 4 的程式化導航。
核心概念
1. router.push 與 router.replace 的差異
| 方法 | 目的 | URL 變化 | 瀏覽器歷史紀錄 |
|---|---|---|---|
router.push(location) |
新增一筆歷史紀錄,類似使用者點擊連結 | 會改變 window.location |
保留前一頁,可使用「返回」 |
router.replace(location) |
取代當前的歷史紀錄,常用於不想留下返回點的情境 | 會改變 window.location |
不保留前一頁,返回會直接跳過此頁 |
簡單記憶法:
push→ 推入新紀錄;replace→ 取代舊紀錄。
2. location 參數的寫法
router.push / router.replace 接受兩種形式的參數:
- 字串:直接寫路徑或具名路由的名稱
router.push('/about') router.replace({ name: 'UserDetail', params: { id: 5 } }) - 物件:可同時提供
path、name、params、query、hash等屬性router.push({ name: 'Search', query: { q: 'Vue Router', page: 2 }, hash: '#result' })
注意:若同時使用
path與name,Vue Router 會優先以name為主。
3. 等待導航完成
router.push / router.replace 皆回傳一個 Promise(Vue Router 4),可以在導航成功或失敗時取得回饋:
router.push('/dashboard')
.then(() => console.log('導航完成'))
.catch(err => console.error('導航失敗', err))
常見的失敗原因包括 重複導航(相同路由已在當前),或是 導航守衛 中的
next(false)。
4. 與導航守衛(Navigation Guard)的配合
在 beforeEach、beforeEnter 等守衛裡,可以使用 next 直接返回 router.push 或 router.replace,實現 條件式重導:
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.isLoggedIn) {
// 未登入,導向登入頁,使用 replace 避免回到受保護頁面
next({ name: 'Login', replace: true })
} else {
next()
}
})
程式碼範例
以下示範 5 個常見且實用的程式化導航情境,均以 Vue 3 + Composition API 為基礎。
範例 1️⃣:表單送出後導向明細頁(使用 push)
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const name = ref('')
const email = ref('')
async function submitForm() {
// 假設呼叫 API 建立使用者
const res = await createUser({ name: name.value, email: email.value })
if (res.success) {
// 導向使用者明細頁,保留返回表單的紀錄
router.push({ name: 'UserDetail', params: { id: res.data.id } })
}
}
</script>
<template>
<form @submit.prevent="submitForm">
<input v-model="name" placeholder="姓名" />
<input v-model="email" placeholder="Email" />
<button type="submit">送出</button>
</form>
</template>
說明:表單送出成功後使用
router.push,讓使用者可以透過瀏覽器「返回」回到表單頁面,方便修正資料。
範例 2️⃣:登入成功後使用 replace 防止返回登入頁
<script setup>
import { useRouter } from 'vue-router'
import { useStore } from '@/store'
const router = useRouter()
const store = useStore()
const credentials = reactive({ username: '', password: '' })
async function login() {
const ok = await store.dispatch('auth/login', credentials)
if (ok) {
// 登入成功,使用 replace 替換當前的登入頁紀錄
router.replace({ name: 'Home' })
} else {
alert('帳號或密碼錯誤')
}
}
</script>
<template>
<form @submit.prevent="login">
<input v-model="credentials.username" placeholder="帳號" />
<input v-model="credentials.password" type="password" placeholder="密碼" />
<button type="submit">登入</button>
</form>
</template>
說明:使用
replace後,使用者按「返回」不會再回到登入頁,提升安全性與使用體驗。
範例 3️⃣:條件式導向(根據 API 回傳的狀態)
<script setup>
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { fetchUserStatus } = '@/api'
const router = useRouter()
onMounted(async () => {
const { status } = await fetchUserStatus()
// 假設 status 可能是 'new', 'active', 'blocked'
if (status === 'new') {
router.replace({ name: 'Welcome' }) // 新使用者 → 歡迎頁
} else if (status === 'active') {
router.push({ name: 'Dashboard' }) // 正常使用者 → 主控台
} else {
router.replace({ name: 'AccessDenied' }) // 被封鎖 → 拒絕存取頁
}
})
</script>
說明:在
onMounted內根據後端回傳的使用者狀態決定導向路徑,replace用於不希望留下「中繼」頁面的情況。
範例 4️⃣:帶參數與查詢字串的導航
// 在組件或服務中
function goToSearch(keyword, page = 1) {
router.push({
name: 'Search',
query: { q: keyword, page },
hash: '#results' // 直接捲動到結果區塊
})
}
// 呼叫
goToSearch('Vue Router', 3)
說明:
query會自動序列化為?q=Vue%20Router&page=3,hash讓瀏覽器自動定位至#results。
範例 5️⃣:防止「重複導航」錯誤(使用 try...catch)
async function safeNavigate(to) {
try {
await router.push(to)
} catch (err) {
// Vue Router 4 會拋出 NavigationDuplicated 錯誤
if (err.name !== 'NavigationDuplicated') {
console.error('導航失敗', err)
}
// 若是重複導航,可視需求不做任何事
}
}
// 例:使用者點擊同一個列表項目
safeNavigate({ name: 'ProductDetail', params: { id: 42 } })
說明:當使用者多次點擊同一個連結時,
router.push會拋出NavigationDuplicated,透過try...catch可避免不必要的錯誤訊息。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 / 最佳實踐 |
|---|---|---|
重複導航錯誤 (NavigationDuplicated) |
同一路由多次 push 會拋錯 |
使用 try/catch 或在呼叫前比對 router.currentRoute |
忘記傳遞 params 時使用 name |
params 只能與具名路由搭配,使用 path 會被忽略 |
永遠 使用具名路由 ({ name: 'User', params: { id: 1 } }) |
query 參數未編碼 |
手動拼接字串容易出錯 | 交給 Vue Router 處理,直接使用 query 物件 |
在守衛內使用 push 造成無限迴圈 |
若守衛內再次導向相同路由,會不斷觸發 | 使用 replace: true 或檢查 to.fullPath !== from.fullPath |
| 未考慮瀏覽器返回時的狀態 | push 會留下歷史紀錄,返回時可能需要重新載入資料 |
在 onBeforeRouteUpdate 中重新抓取資料,或使用 replace 取代不必要的紀錄 |
建議的開發流程
- 先決定導航意圖:是要保留返回紀錄 (
push) 還是直接取代 (replace)。 - 使用具名路由:可讓
params、query更安全、可讀性更佳。 - 統一錯誤處理:在一個共用的
navigate函式裡包裝router.push/replace,集中處理NavigationDuplicated。 - 結合守衛:在需要權限或資料前置檢查時,於守衛內使用
next({ name: ..., replace: true }),避免在組件內重複寫導向邏輯。 - 測試路由變化:使用 Vue Test Utils 的
router.push模擬,確保導航流程在單元測試中也能正確執行。
實際應用場景
| 場景 | 使用方式 | 為何選擇 push 或 replace |
|---|---|---|
| 使用者完成註冊 | 註冊成功後導向「歡迎」頁,並保留返回至註冊表單的可能 | push(讓使用者可回到表單修正) |
| OAuth 第三方登入 | 第三方授權成功回到應用後直接跳到原本想去的頁面 | replace(避免回到授權回呼頁) |
| 分頁或搜尋結果 | 點擊分頁或改變搜尋關鍵字時改變 URL query,保持瀏覽器前進/返回功能 | push(每次變更都視為新紀錄) |
| 錯誤頁面導向 | API 回傳 404 時自動導向「找不到頁面」 | replace(不想讓使用者返回錯誤的 API 呼叫) |
| 多步驟表單 | 每完成一步即導向下一步的路由,使用 push 讓使用者可返回前一步 |
push(保留歷史) |
總結
程式化導航是 Vue Router 4 中不可或缺的功能,router.push 與 router.replace 為我們提供了兩種不同的歷史紀錄行為,讓開發者可以依需求決定是否保留返回點。掌握 location 物件的寫法、Promise 回傳、以及 與導航守衛的配合,能讓路由控制更彈性且不易出錯。
在實務開發中,建議遵循以下要點:
- 先思考意圖:是否需要返回?使用
push或replace。 - 優先使用具名路由,避免
params失效。 - 統一錯誤處理,尤其是
NavigationDuplicated。 - 合理使用守衛,在權限或資料前置檢查時直接重導。
- 測試與文件化,確保導航流程在不同情境下均可預期運作。
只要把這些概念與範例內化,你就能在 Vue 3 應用中自如地使用程式化導航,為使用者打造流暢且符合預期的單頁體驗。祝開發順利! 🚀