Vue Router

Leo Lin
15 min readNov 23, 2018

--

vue-router是vue的套件,為前端模擬路由,達成切換網址也切換到相對組件的效果。

路由基礎

起手式

假設我們已經透過 vue-cli 建立了 webpack 專案模板
vue-cli 的 webpack 模板本身會詢問是否安裝 vue-router,此為示範手動安裝與基本配置
( 這邊的程式碼可自由安排檔案與位置,達成目標與好管理即可 )

// 在此拆分單一js檔案處理
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)export default new VueRouter({
routes: [
{
name: '組件呈現的名稱',
path: '對應的路徑位置',
component: 放import的組件
},
]
})
// 匯出後即可在根實例或主組件中為其router屬性加上
// 假設這邊加在main.js的根實例
import router from '剛剛處理路由的js檔'new Vue({
el: '#app',
template: '<App/>',
router
})

完成基本配置後,由於使用了 Vue.use( VueRouter ),會在 Vue 中增加<router-view/> 與 <router-link/> 這兩個組件

router-view

渲染路由對應的組件,長這樣:

<!-- 由於在單一檔案組件template因此直接 self closing --><!-- 基本視圖,自動預設name屬性為default -->
<router-view />
<!-- 具名視圖,name屬性會負責渲染相對設定組件 -->
<router-view name="name" />

關於具名視圖下方會介紹

router-link

切換路徑的連結,實際會渲染出一個 a tag,長這樣:

<router-link to="path">連結</router-link>

基礎了解完畢,就來一個簡單的例子 ( 假設基本設定已完成 ):

// router.js設定import One from '@/components/One'
import Two from '@/components/Two'
export default new VueRouter({
routes: [
{
path: '/One',
component: One
},
{
path: '/Two',
component: Two
}
]
})
<!-- App.vue --><script>
import One from '@/components/One'
import Two from '@/components/Two'
export default {
components: {
One,
Two
}
}
<script/>
<template>
<div>
<nav>
<router-link to="/One">One<router-link/>
<router-link to="/Two">One<router-link/>
<router-view/>
<div>
<template/>

動態比對路由

透過 ” vue-router渲染出來的組件實例 ”,實例中會有 $route 屬性 ( 該屬性並不是 vue 實例本身自帶 ),此為路由訊息物件,內容皆為 vue-router 設定中的值,而藉由該屬性的方法可以動態比對。

依賴此實例屬性 ( $route ) 會有一些缺點,詳細參考下方:路由組件 props

這邊先介紹兩種動態比對方法:params 與 query

params

parameter:參數,縮寫為 params
為路徑的一部分
以一個假設網址:http://localhost:8080/#/Products/1 來舉例

// 路由設置
{
path: '/Products/:id?',
component: Products
}

冒號表示後方字串為一個自訂名稱的參數
而我們可以透過 this.$route.params.id 來取得該參數值

// $route為實例屬性
this.$route.params.id // 得到1

最後方的問號表示此參數可有可無
如果沒加上問號,就必須帶上參數才能成功匹配路由渲染出組件
像 http://localhost:8080/#/Products 這樣就無法匹配路由

query

query string 指問號後方的字串
以一個假設網址:http://localhost:8080/#/Products?id=1 來舉例

// 路由設置
{
path: '/Products',
component: Products
}
// 這邊直接不用做配置,可以自動拆分問號後方的組合

也可以取得複數資料
假設網址:http://localhost:8080/#/Products?id=1&second=2

console.log(this.$route.query)
我們可以從query屬性取得我們想要的資料物件
{
id: '1',
second: '2'
}

正規表達式

若需要再複雜一點的匹配也可以搭配正規表達式
可參考 path-to-RegExp

巢狀路由

若要配置巢狀路由,可以透過 chirdren 屬性,內容一樣為陣列裝物件。
注意 /根目錄符號的運用,children 不加讓其自動匹配父路由
( 以下都假設組件都已 import )

{
path: '/About',
component: About,
children: [
{
path: 'WTAPS',
component: WTAPS
},
{
path: 'NBHD',
component: NBHD
},
]
}
// 此時連結為#/About/WTAPS時
// router-view就會匹配顯示WTAPS組件

如果我們想要做路徑只有 About 時,也要顯示 AboutHome 組件,很簡單直覺的將 path 設為空字串即可

{
path: '/About',
component: About,
children: [
{
path: '',
component: AboutHome
},
{
path: 'WTAPS',
component: WTAPS
},
{
path: 'NBHD',
component: NBHD
},
]
}
// 此時連結為#/About
// router-view就會匹配顯示AboutHome組件

具名路由與具名路由視圖

具名路由

除了靠 path 來指定匹配路徑,還能夠指定 name 屬性來使用名稱做匹配。
具名路由雖然寫法較為繁雜,但可讀性較直覺。且現有路由設定已經相當複雜時,可以很快速的設置一個捷徑。設定方式:

// 使用name屬性
{
name: 'testName'
path: '/About',
component: About,
}

router-link 要調整為 v-bind 綁定物件

<router-link :to="{name: 'testName'}">Home</router-link>

注意:

  • name 屬性的值必須是 ‘ 唯一 ’,類似 ID 的概念,router 在對應路徑時才不會衝突 ( 重名時 vue 會報錯 )
  • 使用具名路由匹配,是 vue-router 去找到符合條件的名稱組件,並將其 ‘路徑’ 作為匹配。所以連結顯示的一樣為 path 設定,並不會改成顯示name

帶入參數

router-link 藉由 v-bind 綁定物件,除了匹配具名路由之外,還能夠帶入params 參數屬性

<router-link
:to="{
name: 'testName',
params: {
season: '17aw'
}
}"
>
Home
</router-link>

這樣就能夠在匹配完的組件實例中藉由 $route.params 物件取用參數了

具名路由視圖

router-view 其實有一個 name 的 attribute,只是當沒設定時,預設為 ‘ default ’

<router-view/>
<router-view name="default"/>
<!-- 此兩者作用一模一樣 -->

而路由配置中指定組件除了 component 屬性,指定複數組件我們可以寫components ( 注意多了 s ),內容為物件,其中的 key 值會與 router-view 的name 屬性來匹配渲染

// 路由配置
{
path: '/...'
components: {
a: A,
b: B
}
}

這樣 router-view 就會分別渲染出符合條件的組件

<router-view name="a"/> <!-- 渲染出A組件 -->
<router-view name="b"/> <!-- 渲染出B組件 -->

所以藉由具名視圖,我們可以做到渲染多重組件,假設情境為三個 router-link,link 一對應組件 A, link 二對應組件 B,而 link 三要渲染出組件 A、B 兩者,我們可以這樣設定:

<!-- router-link配置 --><router-link to="/NamedRoute/A">組件A</router-link>
<router-link to="/NamedRoute/B">組件B</router-link>
<router-link to="/NamedRoute/both">both</router-link>
<!-- router-view配置 --><router-view/>
<router-view name="both"/>
// 路由配置[
{
path: '/NamedRoute/A'
component: A
},
{
path: '/NamedRoute/B'
components: {
default: B //這邊只是示範單獨指定具名視圖
}
},
{
path: '/NamedRoute/both'
components: {
default: A,
both: B
}
}
]

這時候 components 中的兩個 key:default 與 both,就分別對應到兩張視圖,第二張視圖只針對 both,所以單獨點選 A 或 B 都不會渲染。

轉址與別名

轉址

當匹配了該路徑,會自動‘轉換’至指定的路徑。
路由設置中,藉由redirect屬性來達成轉址

// 路由設置
// 轉址設定為獨立物件,表示匹配該路徑時,會自動跳轉到另外指定的路徑
// 路徑也會跟著變換
{
path: '/aboutus',
redirect: '/About/us'
}
// 若連結輸入/aboutus
// 自動跳轉至/About/us路由頁面

redirect屬性除了字串,也可以寫成物件或是函數。

物件寫法主要是指定具名路由

{
path: '/aboutus',
redirect: {
name: 'AboutUs'
}
}
// 當符合匹配結果,會尋找name為AboutUs的路由
// 跳轉到該路由path設定的路徑
// (像是router-link找name的運作方式)

寫成函式,接收一個物件作為參數,return 運算結果

{
path: '*', // '*'表示任意字串
redirect: from => {
// return '/'
// 除了return字串,也可以return物件來指定具名路由
return {
name: 'AboutUs'
}
}
}

藉由路由多重匹配成功時 “ 只套用第一個 ” 的特性,再轉址設計就可以運用,例如:

{
path: '*',
redirect: '/'
// 雖然前面設定都符合'*'任意字串,但前面會優先匹配成功而被套用
// 最後所有條件都不符合時就自動轉址至根目錄頁面
}

別名

alias 中譯:別名、化名
路徑不變,代表一個路由有多個匹配路徑的名稱

// 路由配置
{
path: 'About'
// alias: 'about',
alias: ['about', 'story', 'test']
// 很合理的,別名可以有很多個,因此可以寫成物件
component: ...
}
// 只要路徑符合上述path或alias中字串,就渲染出該組件

注意!!!alias 即使大小寫不分,也不能與 path 相同,否則報錯

路由組件 props

上面說到 vue-router 會給渲染出的組件實例帶有 $route 屬性。而組件依賴 $route 的情況導致該組件運作限制於某些 URL,因此 vue-router 提供解決這種情況的 props 屬性,該屬性有三種模式:

  • 布林模式
  • 物件模式
  • 函數模式

而要讓組件接收 props 時,一樣要在組件 props 中設定對應的 prop

布林模式

// 組件A舉例
export default {
props: ['id']
}
// 路由設置
{
path: '/Product/:id?',
component: A,
props: true // 在這邊設定props屬性為true
}

如此一來,網址中 ” /Product/xx ” 後方的字串就會自動作為 A 組件的 props 注入其中,完全分離組件對於 $route 的依賴。
注意:此方法無法用 query string 帶入參數

物件模式

延續上例,如果不需要藉由網址來注入參數,而是直接注入固定參數,可以使用物件寫法:

{
path: '/Product', // 這邊參數就移掉了
component: A,
props: {
id: 3, // 直接指定參數注入組件
test: 't' // 如果這邊注入props沒有設定的東西,組件是收不到的
}
}

函數模式

使用函式,最大彈性來自訂注入的props
該函式會傳入一個route物件參數,就如同注入組件實例的$route物件一樣,我們可以基於此物件來自由的自訂props

{
path: '/Product',
component: A,
props: route => {
return {
// 藉由route物件上的各種屬性,可以有更高自由度
id: route.query.id,
test: route.params.test
}
}
}

vue-router 的模式

vue-router 分為三種模式:hash、history、abstract ( node.js )
我們可以在傳入的參數物件中寫上 mode 屬性
( mode 預設為 hash )

// 來到我們管理路由的檔案router.js
export default new VueRouter({
mode: 'history', //若不寫mode就預設為hash模式
routes: [
......
]
})

hash 模式 ( 預設 )

特色為 # 字號分隔路由,原因在於井字號後方不會被 URL 解析,所以可以拿來操作。若省略井字號分隔,路徑會自動解析去找資料夾。
想像一個路由如果為 “ /About/Us ” ,打包後的 distribution 版本若是直接輸入網址,直接解析路徑會是去 About 資料夾找 Us 檔案,我們不是要讓這樣的事情發生,所以透過井字號的做法來模擬路由。

history 模式

如果省略井字號,就是 history 模式。該模式乾淨整潔,但是代價為後端伺服器必須設置 URL Rewrite 重新將路徑指向 index.html ( SPA只在此頁 )。若後端配合好,則 history 模式當然會比較好看,後端無法配合則需採用 hash 模式。
注意:設定為 history 模式,在開發測試環境會自動幫你做到 URL Rewrite,實際上線版本還是要再做設定的不要忘記

abstract 模式

支持所有 JavaScript 環境,如 node.js。當發現沒有瀏覽器 API 時,會強制切換至此模式。

  • 想要了解以上各模式詳細及注意事項可以再參考官方文件

注意事項

注意分辨根目錄

/符號表示根目錄
設置巢狀路由時要記得使用與否

路徑有多重匹配符合

會套用到第一個符合的結果

常見應用與API

參考官方API文件

自動套用 active

vue 會為目前匹配路由的 router-link 套用 router-link-active 與 router-link-exact-active 這些 class,可以直接方便的定義 active 的樣式 ( 改名或要影響外層可以參考官方文件 )

自訂切換路由事件

透過 vue 實例原型中的 $router 屬性,我們可以取得結合了 vue-router 的實例並使用其 API 方法。

以下僅簡單介紹,進階的傳入參數可參考官方文件。

router.back() / router.forword()

很簡單的方法,上一頁與下一頁。使用方式:

export default {
methods: {
method(){
// 直接調用方法即可

// 上一頁
this.$router.back()
// 下一頁
this.$router.forword()
}
}
}

router.go( n )

與上一頁下一頁行為很接近,差別在於傳入的數字參數可以較彈性的指定 上 / 下一頁 的頁數。使用方式:

export default {
methods: {
method(){
// 直接調用方法即可

// 上3頁
this.$router.go(-3)
// 下2頁
this.$router.go(2)
}
}
}

router.push( location )

跳轉至指定頁面。使用方式:

// 情境: 登入成功後跳轉頁面
// 來到一個登入頁面組件Login.vue
// template中的登入按鈕
<button @click="login">login</button>
// script
export default {
methods: {
login(){
this.$http.post(api, this.user)
.then( ({data}) => {
// 以上都不是重點,重點在此
this.$router.push('/')
// 透過此方法,可以直接跳轉到根目錄
})
}
}
}

router.replace( location )

與 router.push 行為很類似,差別在於其為替換當前組件,因此跳轉行為 ” 不會被記錄 ”,上一頁下一頁會忽略。使用方式:

export default {
methods: {
method(){
// 看似跳轉頁面,但其實此操作不會被記錄
// 若此時切換至index之類的操作
// 再按上一頁並不會回到about組件
this.$router.replace('/about')
}
}
}

以上如本人理解有誤歡迎指證或討論,謝謝!

--

--

Leo Lin
Leo Lin

Responses (1)