vue3 快速入門系列 - vue3 路由
在vue3 基礎上加入路由。
vue3 需要使用 vue-router V4
,相對於 v3,大部分的 Vue Router API 都沒有變化。
Tip:不瞭解路由的同學可以看一下筆者之前的文章:vue2 路由
參考:vue2 路由官網、vue3 路由官網
vue-router V4
在 Vue Router API 從 v3(Vue2)到 v4(Vue3)的重寫過程中,大部分的 Vue Router API 都沒有變化,但是在遷移你的程式時,你可能會遇到一些破壞性的變化 —— 從 Vue2 遷移
vue3 需要使用 vue-router 4.x.x 版本。安裝:
PS hello_vue3> npm i vue-router
changed 37 packages, and audited 69 packages in 3s
8 packages are looking for funding
run `npm fund` for details
1 moderate severity vulnerability
To address all issues, run:
npm audit fix
Run `npm audit` for details.
版本:
"dependencies": {
"vue": "^3.4.15",
"vue-router": "^4.3.0"
},
第一個示例
在vue3專案中加入路由。
步驟如下:
- 建立路由
src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
// new Router 變成 createRouter
const router = createRouter({
// mode: 'history' 配置已經被一個更靈活的 history 配置所取代
// 必填。否則報錯:Uncaught Error: Provide the "history" option when calling "createRouter()"
history: createWebHistory(),
routes
})
export default router
Tip:new Router 變成 createRouter
來建立路由;其中模式
需要透過呼叫方法建立,必填
。
- 建立兩個元件
<template>
<div>
<h1>About</h1>
// 可以透過設定router-link-active類來為被選中的路由新增樣式
<router-link to="/">to Home</router-link>
</div>
</template>
<template>
<div>
<h1>Home</h1>
<router-link to="/about">to About</router-link>
</div>
</template>
- App.vue 中引入
<router-view>
告訴 Vue Router 在哪裡渲染匹配到的元件。
<template>
<router-view></router-view>
</template>
<script lang="ts" setup name="App">
</script>
- main.ts 透過 use 使用路由
import {createApp} from 'vue'
import App from './App.vue'
// 會自動載入 ./router/index.ts
import router from './router'
createApp(App)
// 將 Vue Router 外掛安裝到 Vue 例項中,以便在整個應用程式中使用 Vue Router 的功能
// Vue.use(MyPlugin) - 呼叫 `MyPlugin.install(Vue)`
.use(router)
.mount('#app')
接著就可以透過瀏覽器體驗:
Home
// 點選,調整到 about 路由
to About
About
// 點選,調整到 home 路由
to Home
Tip: 透過 .use(router)
在 vue 開發者工具中就會看到路由tab
。
命名路由
Tip: vue2 路由 -> 命名路由
路徑有時太麻煩,可以使用命名路由
替代。
例如將 About 從路徑改為名稱跳轉。核心程式碼如下:
// 定義 name
{ path: '/about', component: About, name: 'guanyu' },
// 跳轉
:to="{name: 'guangyu'}"
Tip:to 目前有2種寫法,感覺字串方式很痛快,物件還需要寫好多,但是到子路由或傳遞引數,會發現還是物件好用。
// 傳遞字串 - 理解為目標路由的路徑
to="/"
// 傳遞物件
:to="{path: '/'}"
:to="{name: 'guangyu'}"
巢狀路由
Tip:和 vue2 中路由用法相同,詳情請看:vue2 路由 -> 巢狀路由
新建一個 Article 元件,裡面定義一個 router-view。請看示例:
- Article.vue
<template>
<div>
<h1>Article</h1>
// path 需要將一級路由路徑加上,例如 /article,不能只寫 detail。該 name 也方便。
// query 效果:http://localhost:5173/article/detail?id=1
<router-link :to="{ path: '/article/detail', query: { id: 1 } }">文章 id1 詳情</router-link><br>
<router-link :to="{ path: '/article/detail', query: { id: 2 } }">文章 id2 詳情</router-link><br>
// 注:將物件換成字串,效果相同
<router-link to="/article/detail?id=3">文章 id3 詳情</router-link><br>
<router-view></router-view>
</div>
</template>
<script lang="ts" setup name="App">
// 可以不引入
// import {RouterView,RouterLink} from 'vue-router'
</script>
Tip:可以不引入 import {RouterView,RouterLink} from 'vue-router'
- Detail.vue
<template>
<div>
<h1>文章id: {{ $route.query.id }}</h1>
</div>
</template>
- 增加路由和子路由。子路由的 path 無需增加
/
const routes = [
{ path: '/home', component: Home,},
{
path: '/article',
component: Article,
children: [
{
path: 'detail',
component: Detail
}
]
},
]
- Home.vue 增加Article入口
<router-link :to="{name: 'guanyu'}">About</router-link>
<br>
<router-link :to="{path: '/article'}">Article</router-link>
測試:進入Home,點選 Article,點選 文章 id1 詳情
,顯示 文章id: 1
,測試透過。
路由 query 引數
在”巢狀路由“中我們是這樣取得 query 引數:<h1>文章id: {{ $route.query.id }}</h1>
js 中透過 useRoute
hooks 取得 $route。請看示例:
<template>
<div>
<h1>文章id: {{ $route.query.id }}</h1>
<h1>文章id: {{ query.id }}</h1>
</div>
</template>
<script lang="ts" setup name="App">
import {toRefs} from 'vue'
// 返回當前的路由地址。相當於在模板中使用 $route。
// useRouter 返回路由器例項。相當於在模板中使用 $router
import {useRoute} from 'vue-router'
const route = useRoute()
// route: Proxy
console.log('route: ', route);
// 錯誤:解構需要用到 toRefs,否則頁面不會更新
// const {query} = route
// 正確:解構
const {query} = toRefs(route)
</script>
Tip:如果需要解構,需使用 toRefs。若想將 query.id 中的 query 去掉,可以使用後面章節的 路由 props 屬性
,程式碼將更優雅
路由 params 引數
Tip:請看 vue2 路由 -> $route.params
將上節 query 引數示例改成 params。
- params需要增加
佔位符
,比如:id
{
path: '/article',
component: Article,
children: [
{
name: 'xiangxi',
path: 'detail/:id',
component: Detail
}
]
},
- id傳遞方式調整一下,不用 query 那種方式:
<router-link to="/article/detail/4">文章 id4 詳情</router-link><br>
- 接收 id
<h1>文章id: {{ $route.params.id }}</h1>
注
:params 不能傳陣列或物件;/a/:b/:c
,則你必須傳 /a/1/2,如果傳 /a/1 則報錯,如果有時沒有c 可傳,可以改成 /a/:b/:c?
物件形式
將 to 改成物件形式:
<router-link :to="{
path: '/article/detail/4',
params: {
id: 5
}
}">文章 id5 詳情</router-link><br>
瀏覽器報錯更容易理解,說 path 被忽略:
// vscode 報錯:
物件字面量只能指定已知屬性,並且“params”不在型別“RouteLocationPathRaw”中。
// 瀏覽器報錯
[Vue Router warn]: Path "/article/detail/4" was passed with params but they will be ignored. Use a named route alongside params instead.
將 path 改成 name即可:
<router-link :to="{
// path: '/article/detail/4',
name: 'xiangxi',
params: {
id: 5
}
}">文章 id5 詳情</router-link><br>
路由 props 屬性
不就是想接收 params 或 query 傳來的引數的,還得寫這麼一大塊程式碼,太麻煩:
<template>
<div>
<h1>文章id: {{ query.id }}</h1>
</div>
</template>
<script lang="ts" setup name="App">
import {toRefs} from 'vue'
import {useRoute} from 'vue-router'
const route = useRoute()
const {query} = toRefs(route)
</script>
可以透過 props
解決。細節如下:
props 布林
- 定義 props
{
name: 'xiangxi',
path: 'detail/:id',
component: Detail,
// 透過 props 屬性來將路由引數傳遞給元件
// 底層好些這樣:<Detail id=5/>
props: true
}
- 直接透過 defineProps 接收
<template>
<div>
<h1>文章id: {{id }}</h1>
</div>
</template>
<script lang="ts" setup name="App">
defineProps(['id'])
</script>
props 函式
如果需要接收 query,需要用 props 函式,引數是 route,返回需要接收的物件:
// RouteLocationNormalized 是 Vue Router 中的一個型別,它用於描述路由的位置資訊
import { type RouteLocationNormalized } from 'vue-router';
{
name: 'xiangxi',
path: 'detail',
component: Detail,
// 透過 props 屬性來將路由引數傳遞給元件
// props: true
props(route: RouteLocationNormalized ) {
return route.query
}
}
- 觸發路由從 params 改成 query:
<router-link :to="{
name: 'xiangxi',
query: {
id: 5
}
}">文章 id5 詳情</router-link><br>
- 接收方式不變:
<template>
<div>
<h1>文章id: {{id }}</h1>
</div>
</template>
<script lang="ts" setup name="App">
defineProps(['id'])
</script>
Tip:其實 props: true
就相當於下面這段程式碼:
props(route: RouteLocationNormalized ) {
return route.params
}
props 物件
props 還可以寫成物件,但用得較少:
props: {
id: 100
}
replace
HTML5的歷史API包含了pushState(),replaceState()和popstate事件
路由預設是 push。比如啟動第一個示例
,未點選 home 或 about 導航時,瀏覽器左上方既不能前進也不能後退
,因為棧中只有當前頁面,指標
沒地方去。在你點選home和about導航後,就可以前進和後退,即使重新整理頁面,這個歷史記錄也不會變。
<router-link :to="{name: 'guanyu'}">About</router-link>
<br>
<router-link :to="{path: '/article'}">Article</router-link>
vue-router 的 replace 作用和用法和 react replace 相同。
現在點 About 就會直接替換
<router-link replace :to="{name: 'guanyu'}">About</router-link>
程式設計式導航
Tip:vue2 路由 -> 程式設計式導航
三秒後跳轉到 article:
<script lang="ts" setup name="App">
import { useRouter } from 'vue-router'
const router = useRouter()
type Path = string
// 說vue2 中程式設計式導航重複跳轉會報錯,vue3中沒這個問題
function to(path: Path){
router.push(path)
}
setTimeout(() => {
to('/article')
}, 3000)
</script>
程式設計式導航使用頻率大於宣告式導航(<router-link :to="...">
)
to也支援物件
,和宣告式導航用法相同,更多介紹請看:vuer-router v4 程式設計式導航
其他
路由元件和一般元件
路由元件
通常放在 pages 或 views 資料夾中,一般元件
通常放在 components 資料夾中 —— 一般開源的專案都會這樣分類
看一個元件是哪種,需要看其如何用。比如定義一個 Demo.vue,如果透過標籤 <Demo/>
這種寫法來使用,就屬於一般元件,如果該元件透過路由渲染,則稱為路由元件。
解除安裝和掛載
透過導航,視覺上消失的路由元件,預設被解除安裝,需要用的時候在掛載。
在 第一個示例
中給 About.vue 增加兩個生命週期鉤子,再次切換 Home 和 About 元件,就能看到效果:
<template>
<div>
<h1>About</h1>
<router-link to="/">Home</router-link>
</div>
</template>
<script lang="ts" setup name="App">
import {onMounted, onUnmounted} from 'vue'
onMounted(() => {
console.log('About 掛載了');
})
onUnmounted(() => {
console.log('About 解除安裝了');
})
</script>
路由模式
history 模式
url 美觀,後期上線,需要服務端配合處理路徑問題,否則重新整理會有404。當使用者在瀏覽器中直接訪問一個路由,或者重新整理頁面時,如果伺服器端沒有正確配置,可能會導致 404 錯誤,因為此時伺服器會嘗試去尋找對應的檔案或路由路徑,而實際上這個路徑是由前端控制的,並不一定存在於伺服器端的檔案系統中。為了解決這個問題,你需要在伺服器端配置一個萬用字元路由,將所有的路由請求都指向你的應用的入口檔案(比如 index.html),這樣就會確保 Vue Router 能夠正確地處理路由請求。
如果你使用的是 Node.js 伺服器,可以使用 Express 框架來進行配置,示例程式碼如下所示:
const express = require('express');
const path = require('path');
const app = express();
// 靜態資源目錄,例如你的 CSS、JavaScript 檔案等
app.use(express.static(path.join(__dirname, 'public')));
// 萬用字元路由,將所有的路由請求都指向 index.html
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// 啟動伺服器,監聽埠
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
這樣配置後,無論使用者訪問哪個路由,伺服器都會返回 index.html,然後 Vue Router 就可以根據路由配置來正確地渲染相應的元件,避免了重新整理頁面時出現的 404 錯誤。
Hash 模式
在 SEO 最佳化方面相對較差。
- 比如
不利於搜尋引擎爬蟲
:Hash 模式下,URL 中的雜湊部分(#後面的內容)不會被包含在 HTTP 請求中,因此在伺服器接收請求時,雜湊部分對於伺服器來說是不可見的。這意味著搜尋引擎爬蟲無法直接獲取到 URL 中的實際內容,因為爬蟲主要是透過 HTTP 請求獲取頁面內容的,所以無法獲取到 hash 後面的內容,這樣就會導致搜尋引擎無法正確地索引和解析頁面。
雖然使用 history 模式相對於 hash 模式在 SEO 最佳化方面有所改善,但它仍然是單頁應用(SPA),可以和服務端渲染結合
沒有匹配到指定的路徑 /
配置如下路由,第一次開啟,瀏覽器控制檯有警告:main.ts:9 [Vue Router warn]: No match found for location with path "/"
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About, name: 'guanyu' }
]
可以透過重定向解決。就像這樣:
const routes = [
{ path: '/', redirect: '/home'},
{ path: '/home', component: Home,},