vue3 快速入門系列 —— vue3 路由

彭加李發表於2024-04-09

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>

程式設計式導航

Tipvue2 路由 -> 程式設計式導航

三秒後跳轉到 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,},

相關文章