http://example.com/user/1#profile 裡,你覺得 #profile 會被傳出去嗎?/user/1,使用者直接在瀏覽器按重新整理,伺服器會收到什麼?它找得到對應的檔案嗎?ref 裡,還是網址裡?如果把網址分享給朋友,畫面要能還原,這會影響你的選擇。Vue Router 讓不同網址對應到不同元件。/user/:id 裡的 :id 是動態參數,/user/1、/user/2 會使用同一個元件,只有資料不同。
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import UserDetail from '../views/UserDetail.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/user/:id', component: UserDetail }, // :id 是動態參數
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
導覽連結用 RouterLink,對應的元件顯示在 RouterView 裡。這部分寫在 App.vue 的 template 中:
// App.vue 的 template 部分,寫成 template 字串示意
// <nav>
// <RouterLink to="/">首頁</RouterLink>
// <RouterLink to="/user/1">使用者 1</RouterLink>
// </nav>
// <RouterView />
在 UserDetail.vue 裡用 useRoute() 讀取參數:
// UserDetail.vue 的 script setup 部分
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
// route.params.id 是字串,例如 '1'
// 用 computed 包起來,從 /user/1 切到 /user/2 時會自動更新
const userId = computed(() => route.params.id)
這裡有一個容易忽略的地方:路由參數的值都是字串。需要數字時用 Number(route.params.id) 轉換。另外,從 /user/1 切到 /user/2 時,元件不會重新建立,所以要用 computed 或 watch 來響應變化。
兩種模式都能讓網址改變時頁面不重新載入,差別在網址長相,以及伺服器要做的事。
| Hash 模式 | Web(History)模式 | |
|---|---|---|
| 網址範例 | /#/user/1 |
/user/1 |
| 建立方式 | createWebHashHistory() |
createWebHistory() |
| 伺服器設定 | 不需要 | 需要 |
| SEO | 較不利 | 較有利 |
Hash 模式:# 後面的內容瀏覽器不會傳給伺服器,伺服器只會收到 /,因此直接回傳 index.html。之後由 Vue Router 讀取 # 後面的路徑,決定顯示哪個元件。
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes,
})
Web 模式:網址是乾淨的 /user/1,但它是真的路徑。使用者直接輸入網址或重新整理時,瀏覽器會向伺服器請求 /user/1。如果伺服器找不到對應檔案,就會回傳 404,所以部署時要設定:不管路徑是什麼,都回傳 index.html。
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes,
})
怎麼選?
index.html。?page=0&sortBy=ctime 叫做查詢參數(query string)。它不影響使用哪個路由,只是附加在網址後面的資料。
? 之後開始是查詢參數& 分隔多組參數= 左邊是 key,右邊是 value,value 永遠是字串URLSearchParams 解析:const params = new URLSearchParams('page=0&sortBy=ctime')
params.get('page') // '0'
params.get('sortBy') // 'ctime'
把篩選條件放在網址裡的好處是,網址就是唯一的資料來源。使用者重新整理頁面,或把網址分享給別人,畫面都能還原。
下面是商品列表的範例,過濾條件和分頁都從 route.query 讀取,修改時則用 router.push:
// ProductList.vue 的 script setup 部分
import { computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
// 從網址讀取,值都是字串,需要轉換
const page = computed(() => Number(route.query.page ?? 0))
const sortBy = computed(() => route.query.sortBy ?? 'ctime')
// 改變排序:改網址,頁數重設為 0
function changeSort(value) {
router.push({
query: { ...route.query, sortBy: value, page: 0 },
})
}
// 換頁:只改 page,保留其他條件
function changePage(p) {
router.push({
query: { ...route.query, page: p },
})
}
function fetchProducts() {
// 這裡換成你的 API,用 page.value 和 sortBy.value 帶參數
console.log('取得商品', page.value, sortBy.value)
}
// 網址變化時重新取資料,immediate 讓進入頁面時先執行一次
watch(() => route.query, fetchProducts, { immediate: true })
模板部分的寫法一樣直接,綁定 sortBy 與分頁按鈕即可。
這裡有幾個重點:
page 重設為 0,避免停在不存在的頁數。router.push 換成 router.replace。:id 讓同一個元件對應多個網址,參數是字串,變化時要用 computed 或 watch 響應。index.html。