[Vue 3] 第三課 Vue Router和Vue Axios
Adrian Chen

開始之前

其實這兩個東西互相之間並沒有什麼關聯,一個是Vue 3官方推薦的路由管理工具,一個是Vue 3中使用人數最多的HTTP請求發送工具。 為什麼筆者要將這兩個東西放在一起去講呢? 有兩個原因,第一個是因為之前兩節課,已經把Vue的基礎使用、Vue組件這兩個Vue中最重要的概念完成了,因此剩下一些瑣碎的東西,大約就是這兩個很重要了。 第二是因為這兩個東西都屬於Vue生態(Ecosystem)中很重要的環節,筆者希望讀者能夠藉此機會熟悉Vue 3強大的可拓展性。

Vue Router

在講到這個東西之前,讀者需要先瞭解路由(Router)這個概念。在傳統的 Web 開發過程中,當需要實作多個站內頁面時,需要寫很多 HTML 頁面,然後透過 <a> 標籤來實現互相跳躍。

在現今化模式下的前端開發,像 Vue ,可以輕鬆實現只用一個 HTML 檔案,卻能夠完成多個站內頁面渲染、跳轉的功能,這就是路由。

放到Vue中,路由的實現,是透過掛載不同的頁面組件來實現的。

要在Vue中實現路由的功能,我們要使用到Vue Router

Vue Router的安裝和基本使用

  1. 使用npm安裝Vue Router
1
npm install vue-router@4
  1. 在專案的根目錄下創建兩個目錄:router和views
1
mkdir router views
  1. 在router下新建router.js檔案:
1
touch router/router.js
  1. 在views下新建測試組件並填入一些內容:
1
2
touch views/test.vue
echo "<script>\n</script>\n<template>\n<div>Hello, world.</div>\n</template>\n<style>\n</style>" >> views/test.vue
  1. 在router.js中填入以下內容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createRouter, createWebHistory } from 'vue-router';

// 定義路由集合
// name:路由的名稱,path:路由的路徑,component:該路由對應的頁面組件
const routes = [
{ name: "test", path: "/test", component: () => import('../views/test.vue') }
]

// 定義路由轉換器
// history:web history,routes:標準寫法是routes: routes,但如果上面定義的路由集合的名字爲routes,就可以簡寫爲routes
const router = createRouter({
history: createWebHistory(),
routes
})

// 導出路由轉換器
export default router
  1. 在主配置main.js中引入路由轉換器
1
2
3
4
5
6
7
// main.js
...
import router from './router/router.js';

const app = createApp(App)
app.use(router)
...
  1. 在App.vue根組件中添加<router-view></router-view>標籤。

完成了以上步驟,當我們打開瀏覽器,進入http://localhost:5173/test ,就可以看到顯示Hello, world.訊息的路由頁面了。

路由嵌套

從上面的配置中,我們不難看出,路由和頁面組件是一一對應的關係。在任一Vue App中,都有一個基本路由:path爲/對應App.vue。因此,/也被稱爲根路由,App.vue也被稱爲根組件。

而我們新增的路由/test對應的頁面組件是test.vue。但是,上面的步驟中我們也看到,要想將新路由在頁面上顯示出來,仍然需要在根組件上添加<router-view></router-view>這個標籤。這裏就涉及到一個不是很好理解的知識點——路由嵌套。

上面的例子中,我們可以將/test中的斜線看作是(事實上也就是)根路由/,也就是說,/test路由實際上是“掛載”在根路由上,對應着的,其組件test.vue也要相應放到根組件中才能顯示。這就是路由嵌套,即子路由必須要掛載到父路由中才能夠藉由父路由的顯示而顯示。

接下來我們繼續思考一個問題:我們剛剛只不過是在App.vue中添加了一個\\</router-view>標籤,用以代表App.vue這個組件對應的路由(也就是根路由)下掛載的子路由對應的組件。那麼,是否是只要開啓了路由,App.vue中就只能放一個\\</router-view>標籤呢?答案顯然是否定的。讓我們爲App.vue加一點東西:

1
2
3
4
<template>
<div>這是原本顯示在根組件中的內容哦!</div>
<router-view></router-view>
</template>

顯示效果如下:
image

我們可以看到,原本在App.vue中的內容也正確地被顯示了。因此,我們得出了一個令人驚悚的結論——似乎,頁面組件也可以像功能組件那樣,作爲上一級頁面的一部分來顯示,而不是全部。

恭喜你,如果你理解到這一層,那麼代表着你會對路由和頁面組件有了一個比較深入的認識,而不僅僅侷限於一個路由對應一個頁面。好,那麼現在我們總結出來的結論是:一個路由對應一個頁面組件,而不是一個路由對應一個頁面。

也就是說,如果根路由/下除了掛載/test路由,還掛載了其他的什麼別的路由,這些路由也都將作爲根組件的一部分去顯示,當我們在這些路由之間切換的時候,事實上,是根組件中\\</router-view>標籤所代表的頁面組件之間的切換。

OK,理解了這個概念,我們就可以繼續進行下去了。接下來我們講的是:多層嵌套路由。

既然普通的頁面組件能夠放到根組件裏面,那麼頁面組件中是不是也可以繼續放其他的頁面組件呢?當然可以了!畢竟每個子頁面都是父頁面的一部分嘛。所以理論上來講,路由可以一直嵌套下去。比如這一段:/test/demo/0nlineTek/web/...。那麼,多層嵌套路由該怎麼實現呢?很簡單,我們首先要在定義的routes集合裏面爲需要多層嵌套的路由添加children屬性:

1
2
3
4
5
6
7
8
9
10
11
// router.js
const routes = [
{
name: "test",
path: "/test",
component: () => import('../views/test.vue'),
children: [
{ name: "demo", path: "/test/demo", component: () => import('../views/demo.vue') }
]
}
]

然後在test.vue的對應位置再加上一個\\標籤就好了。

動態路由匹配

很多時候,我們需要將給定匹配模式的路由對應到同一個組件。 例如,我們可能有一個 User 組件,它應該對所有使用者進行渲染,但使用者 ID 不同。 在 Vue Router 中,我們可以在路徑中使用一個動態欄位來實現,我們稱之為路徑參數

1
2
3
4
5
// router.js
const routes = [
// 動態欄位以冒號開始
{ path: '/users/:id', component: User },
]

那麼現在,/users/xiaoming/users/xiaogang這類的路由均會映射到User組件。

我們在User組件內部,也可以使用$route.params.id拿到欄位id的值。

編程式導航

如果我們想要在一個路由下實現跳轉到另一個路由,當然可以使用\標籤,但更推薦的方式是使用編程式導航:

1
2
3
4
5
6
7
import { useRouter } from 'vue-router';

const router = useRouter()

const navigateTo = (route) => {
router.push(route)
}

其中, useRouter().push()方法可以從當前路由跳轉到其餘路由。其引數可以是路由的name或者路由的path。

如果你想要在跳轉到另一個路由的時候,向另一個路由傳送引數,可以使用params或query引數:

1
2
3
4
5
// 當使用params引數時,不能配合path,必須使用name,params引數不會顯示到url中,但重新整理頁面後params引數失效。(經過測試,此方法暫不可用,似乎是Vue Router的bug)
router.push({name: 'User', params: { username: "xiaoming" }})

// 當使用query引數時,可以配合path或name,query引數一定會顯示到url中,但重新整理頁面後query引數會得到保留。
router.push({path: '/user', query: { username: "xiaoming" }})

終點路由用useRoute().query接收。

導航守衛

在瞭解這個知識之前,我們首先要給自己定下一個規矩:好好設計路由結構,路由結構切勿過於複雜。能用功能組件解決的問題,就儘量別用路由。否則,下面要講的東西以後用起來,一定會要了你的命。

我們通常都有這樣的需求:只有用戶完成了登入動作,纔能進入某些路由顯示頁面,否則就重定向到登入頁面。

這個時候,我們就需要一把“鎖”來守衛住我們的路由,只有符合條件,纔將路由放行,其餘情況下統統攔下。Vue Router的導航守衛功能應運而生。

簡單來講,所謂的導航守衛,就是在用戶不論透過什麼方式(不管是頁面內導航,還是直接在位址欄中輸入url)進入路由前,加裝一個檢查站攔截用戶的流量,將現有條件和預設條件進行比對,滿足預設條件則放行,反之則不放行。

我們一般使用的導航守衛都是全局前置守衛:

1
2
3
4
5
6
7
8
9
10
11
router.beforeEach(async (to, from) => {
if (
// 检查用户是否已登录
!isAuthenticated &&
// ❗️ 避免无限重定向
to.name !== 'Login'
) {
// 将用户重定向到登录页面
return { name: 'Login' }
}
})

接下來筆者解釋一下回呼函式中兩個引數的意義:

  1. to:表示即將要跳轉到的路由物件。
  2. from:表示從哪個路由物件跳轉的。

如果有讀者去閱讀Vue Router的相關文檔,請忽略next引數,這個引數官方並不推薦使用,也不一定會在哪一版本中移除。

Vue Router相關內容就講到這裏。其餘內容請自行參閱Vue Router官方文檔。

Axios

在原生JS中,我們使用JQuery包中提供的$ajax()函式來向後端發送請求。那麼在Vue 3中,我們使用和它非常相像的一個工具,名叫axios。

  1. 安裝axios
1
npm install --save axios
  1. 使用axios發送請求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import axios from 'axios';

axios({
url: "https://api.example.com/api",
method: "POST",
data: {
need: "yes"
}
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})