Vue Router 路由拆分與嵌套

Lastor
Code 隨筆放置場
9 min readApr 6, 2020

這幾天摸 Vue,嘗試將 routes 進行拆分,在嵌套 (Nested) 的部分摸了一陣子才搞明白,這邊稍微給自己做個紀錄。

  1. Vue Router 基本概念
  2. 路由拆分
  3. 巢狀嵌套 (Nested Routes)
  4. *子路由,共用 layout 選用 Component (有特殊技巧)
  5. 子路由,共用中間件

為了簡化內容,下面以 vue-cli 建構的專案為前提,不涉及手動建置 Vue Router,並且省略掉其他非重點的 code。

Vue Router 基本概念

Vue 本身具有 HTML 模板引擎的功能,讓我們可以在 HTML 當中寫入變數,之後就可以透過 JS 去控制、修改變數,HTML 頁面上的內容也會即時性的做出變化。

<div id="app">
<h1>{{title}}</h1> // 最終渲染出 Hello World
</div>
<script>
new Vue({
el: '#app',
data: {
title: 'Hello World' // 使用變數綁定頁面資料
}
})
</script>

Router 功能可以簡單看做是模板的延伸強化版,控制的對象延展為 Component,控制源則變為 routes。讓我們可以指定哪個路由,渲染哪個 Component。

感覺做圖實在有點花時間,用畫得比較快......
// App.vue
<div id="app">
<router-view/> // 視圖接口,渲染時會被替換成對應之 component
</div>
// router/index.js
const router = new VueRouter({
routes: [
{
path: '/users',
component: () => import('../views/Users.vue')
},
{
path: '/products',
component: () => import('../views/Products.vue')
},
// 其他 routes...
]
})
export default router

路由拆分

在未使用巢狀嵌套 (Nested Routes) 的情況下,想要拆分路由是很簡單的事情,概念上跟單純拆分 JavaScript code 是一樣的。

比較要注意的是,前端一般會使用 ES6 import / export,而不是後端 Node.js ( CommonJS) 使用的 require / exports。這會在寫法上產生一些歧異。

拆分的寫法其實蠻自由的,看自己喜好。反正最後只要能組出符合 Vue 格式的 routes Array 即可。

輸出路由 Export:
vue-cli 似乎不支援 Node.js 的 module.exports,只能用 ES6 的方式來做。

// router/products.js
export default [
{
path: '/products',
component: () => import('../views/Products.vue'),
},
{
path: '/products/:id',
component: () => import('../views/Product.vue')
},
// 其他 routes...
]

輸入路由 Import:
可以使用 Node.js 的方式,也可使用 ES6。如果不想置頂使用 import 宣告一堆變數,可以考慮改用 require 的寫法,直接就地引入再展開。

// router/index.js
import products from '../views/products.js'
import other from '../view/other.js'
const router = new VueRouter({
routes: [
products,
other
]
})
// 或用 require 寫法
const router = new VueRouter({
routes: [
...require('../views/products.js').default,
...require('../view/other.js').default
]
})

巢狀嵌套 (Nested Routes)

vue-cli 幫我們快速建立好的 Router 架構,是以 App.vue 為最頂層的入口,將 views 引入到 <router-view> 這個 Tag ,把它替換掉。

隨著 Web 的複雜化,可能會出現一個父路由,底下有許多子路由的層級結構。例如這樣:

▽ root
▽ /products
▽ /products/A
▽ /products/B
▽ /users
▽ /users/A
▽ /users/B

Vue Router 提供了 children 設定,可以讓我們完成這樣的層級結構。它的處理方式也是在 component 裡面設置 <router-view>,提供給 Router 當接口。需要做出一個類似 App.vue 的「容器」,讓其他 component 可以匯入。

一層包一層的巢狀嵌套
// ProductsIndex.vue
<template>
<div class="container py-5"> // 可以在這邊設定統一的 padding 等等
<router-view/>
</div>
</template>
// router/products.js
export default [
{
path: '/products',
component: ProductIndex, // 指定到「容器」組件
children: [
{
// 如果希望 "/products" 也有頁面時的設定
path: '',
component: Products
},
{
// 注意,嵌套時 path 不帶 "/"
path: 'A',
component: ProductsA
},
{
path: 'B',
component: ProductsB
}
]
}
]

這樣嵌套之後,可以讓子路由共享同樣的 layout 結構,例如上面的 .container 與 .py-5。

子路由,共用 layout、選用 Component

除了可以共用 .container 之外。當然也可以另外放入其他的 component,例如 Navbar 或是 Sidebar。

<template>
<div class="container py-5">
<Navbar/>
<router-view/>
</div>
</template>

如此一來,就算接到修改 layout 的要求,例如 padding 太多,希望改小一點之類。我們就可以只修改父路由的組件即可,不用累得半死去修改所有的檔案。

另外,也可能碰上子路由大多都有 Navbar,但是少數幾個頁面卻不需要 Navar 這樣的情況。這時有兩種方法可以處理。

第一種是,不要將 layout 不同的子路由,放進 children 即可。

export default [
{
path: '/products',
component: ProductIndex,
children: [
{ path: 'A', component: ProductsA },
{ path: 'B', component: ProductsB }
]
},
// 不要放進 children 即可
{
path: '/products/C',
component: ProductC
}
]

這樣有個缺點,該頁面雖然沒有 Navbar,但 .container 的設定可能是一樣的。這樣子處理的話,ProductC 就沒法共用到 .container。

這部分可以使用一個比較繞的技巧來處理。Vue Router 其實在一個 .vue 檔案可以塞入複數個 <router-view>,並利用 name 來做識別。所以可以將 Navbar 也改用路由的視圖 (view) 引入,就能間接控制它是否渲染。

不需要 Navbar 的頁面,不傳入即可
// ProductIndex.vue
<template>
<div class="container py-5">
<router-view name="nav"/> // 改用 view 來引入
<router-view/>
</div>
</template>
// router/products.js
export default [
{
path: '/products',
component: ProductIndex,
children: [
{
path: 'A',
// 加上 s,就可以指定複數個 router-view
// 使用 name 進行對應,未設定則為 default
components: {
default: ProductsA,
nav: Navbar
}
},
{
path: 'B',
components: {
default: ProductsB,
nav: Navbar
}
},
{
path: 'C',
// 只匯入單組件的話,會判定給 default
// nav 未指定就不會被渲染

component: ProductsC
}
]
}
]

這部分是自己摸出來的應用技巧,不過既然我能想到,應該各路神人也都想過吧。 (笑)

子路由,共用中間件

路由進行 children 嵌套之後,自然就可以共用中間件。

例如後臺管理員頁面,可能每個路由進來都會希望做一個 admin 權限驗證。使用嵌套的話,就不用在每一個路由都寫一次了。

// router/admin.js
function authorizeIsAdmin(to, from, next) {
// ...邏輯判定
next()
}
export default [
{
path: '/admin',
component: AdminIndex,
beforeEnter: authorizeIsAdmin, // 只需在這寫一行即可
children: [
{ path: 'A', component: AdminA },
{ path: 'B', component: AdminB },
{ path: 'C', component: AdminC },
]
}
]

雖然 Vue 官方稱 beforeEnter 為「導航守衛 (Navigation Guards)」。初學 Vue 的時候看的很問號,後來看一些老外文章,直接把這個理解為後端的中間件 (middleware)。真心覺得這樣理解,整個快速又直接。

--

--

Lastor
Code 隨筆放置場

Web Frontend / 3D Modeling / Game and Animation. 設計本科生,前遊戲業 3D Artist,專擅日本動畫與遊戲相關領域。現在轉職為前端工程師,以專業遊戲美術的角度涉足 Web 前端開發。