# 4-1 Vue Router 與前後端路由

重新認識 Vue.js: 008 天絕對看不完的 Vue.js 3.0 指南 (opens new window)

如同前面章節介紹過的,Vue.js 的核心是用來處理「狀態」與「網頁模板」的映射關係。 當整個網站逐漸發展成為應用程式的時候,Vue.js 元件、甚至是頁面之間切換的流程、狀態的邏輯, 若要使用原本我們所熟悉的 v-if:is 指令,就顯得有些力不從心。

而本章所要介紹的 Vue Router ,作為 Vue.js 框架生態圈的一部分,彌補了 Vue.js 核心薄弱的部分, 除了前面提到控制元件的切換邏輯之外,還能搭配前端路由機制來操作瀏覽器上/下一頁的流程, 這使得 Vue.js 也有了能夠建立 SPA 完整單頁式應用的能力。

# 多頁式應用 (MPA) 與 單頁式應用 (SPA)

最早期的網頁幾乎都是以靜態網頁為主,所有資料直接從 Server 端輸出,幾乎不需要做運算,HTML 內容就是單純的文字、圖片等靜態資料,頁面之間各自是獨立的狀態。 後來過了幾年,有了「動態網頁程式」的語言 (如 PHP、ASP 等) 才開始由後端程式語言負責處理頁面邏輯,加上資料庫系統的成熟,使得原本無法紀錄狀態的網頁,可以利用資料庫來記錄狀態及資料。人們這才開始意會到,原來網頁是可以用來開發「應用程式」的。

當網頁逐漸往應用程式發展之後,網站的架構就從原本的「靜態網頁」變成了「動態網頁」,那麼,什麼是動態網頁呢?

假設我們要做一個電商網站,最基本可能就會有「商品列表頁」、「商品說明頁」、「購物車頁」、「結帳頁」... 等類型。 其他的先不說,就拿「商品說明頁」來做例子。

以傳統的 「靜態網頁」 來說,一個商品就會有一個屬於自己的獨立「商品說明頁」,那假如網站上面有一百個商品, 網站的開發者就得分別維護這一百個 「商品說明頁」,即便他們的結構是一樣的,只是內容不同。

而轉換成動態網頁,由於主要是搭配伺服器與資料庫共同運作,網頁的內容會隨著使用者的操作和互動而有所不同, 於是原本一百個靜態的 「商品說明頁」,其實我們只需要一頁商品說明頁的「結構」,再搭配伺服器端與資料庫渲染出不同的內容即可。

# 什麼是路由

那麼,網站的伺服器怎麼知道什麼時候要提供什麼資料給使用者呢? 熟悉網站操作的各位一定知道,就是透過網址的結構。

│
├── 商品列表 /products-list
│   ├── 商品分類 /category-1
│   │   ├── 商品頁  /category-1/product-1
│   │   ├── 商品頁  /category-1/product-2
│   │   └── ... 
│   │
│   └── 商品分類 /category-2
│       ├── 商品頁  /category-2/product-1
│       ├── 商品頁  /category-2/product-2
│       └── ... 
│
├── 首頁 /home
├── 關於我們 /about
├── 購物車 /cart
└── ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

舉例來說,上面這是個很常見的網站結構,我們可以透過網誌 https://xxx.xxx/products-list 來進入商品列表頁,甚至再往下一層還有 https://xxx.xxx/category-1https://xxx.xxx/category-1/product-1 進入分類頁以及商品的說明頁。

像這樣,瀏覽器透過不同的網址,向後端的網頁伺服器發送請求 (Request),而伺服器接收到瀏覽器的請求後, 回應對應的內容給瀏覽器來渲染,這樣的機制我們就稱為網站路由 (Routing) ,管理網站路由的程式通常會被稱作 Router。

網站路由

# 後端路由 vs 前端路由

前面介紹的是傳統後端路由的概念。

而隨著時間發展,前端在整個網頁領域的比例越來越吃重,尤其是自 Ajax 技術問世之後,省下了換頁的成本, 透過 JavaScript 將網頁的內容替換,使用者與網頁畫面的互動不需要重新載入網頁,操作的回饋也更即時。

但同時也遇到了新的挑戰,更新後的內容在網頁重新整理後沒辦法被留存。

過去使用者的習慣,回到前一個步驟通常會去按「上一頁」,但網頁是由 Ajax 更新的資料,根本沒有「換頁」這個行為, 哪裡來的上一頁與下一頁可以給使用者切換。

幸好後來 HTML5 新的規範提供了 History API,可以透過 pushStatereplaceState 來更新 URL, 同時也提供了 state 物件讓開發者存取網頁的狀態,這就使 JavaScript 開始獲得控制路由的權利。

前端路由

需要注意的是,若要將網站原本後端伺服器控制路由改為前端接手時, 需要改寫原本的後端路由規則,將所有網址都指向 Vue.js 的單一入口。

以 Vue.js 的好朋友 Laravel 為例,首先要將所有的入口都指向同一個頁面,再由這頁去渲染 Vue.js:

// routes/web.php
Route::get('/{any}', 'SinglePageController@index')->where('any', '.*');
1
2

接著,在 SinglePageControllerindex 指向 app

class SinglePageController extends Controller
{
  public function index()
  {
    return view('app');
  }
}
1
2
3
4
5
6
7

最後記得在 /resources/views/app.blade.php 新增 <div id="app">...</div> 以及 <script src="{{ mix('js/app.js') }}"></script> 將 Vue.js 以及前端程式的進入點引入到網頁上。

// resources/views/app.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue SPA Demo</title>
</head>
<body>
  <div id="app">
    <app></app>
  </div>

  <script src="{{ mix('js/app.js') }}"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

如此一來,不管瀏覽器從哪個 URL 進入,都能確保看到的是 index 的頁面,此時,就輪到 Vue.js 與 Vue Router 的前端路由來接手畫面的渲染了。

前端路由

小提醒

除了上述的 Laravel 之外,其他後端程式語言也可利用類似做法,如 Rails 可以透過 Rails.application.routes 來指定後端路由、ASP.NET Core 也能透過 routes.MapRoute 來設定路由等。

# Vue Router 介紹

瞭解了後端路由與前端路由的差別後,接著要來介紹本章的主角「Vue Router」。

Vue Router 是 Vue.js 官方提供的前端路由管理器,截至目前 (2020-12-27) 的最新版本為 v4.0.2。 由於 Vue 3.0 開始使用 createApp 建立元件實體,所以 Vue Router v3 以前的版本將無法支援 Vue 3.0。

注意 - Vue Router 版本選擇

為配合 Vue 3.x 的內容,本書所採用的是 Vue Router v4.x (https://next.router.vuejs.org/ (opens new window)),若讀者使用的是 Vue 2.x 版本,請務必選用 Vue Router v3.x (https://router.vuejs.org/ (opens new window)),而兩個版本間使用的 API 也有些許不同。

注意,由於 Vue 3.0 底層核心改寫,所以 Vue Router v3.x 無法相容 Vue.js 3.0

若想知道 Vue Router 新舊版本差異,可以參考 https://next.router.vuejs.org/guide/migration/index.html (opens new window) 一頁。

# Vue Router 安裝

安裝 Vue Router 的方式非常簡單。

如果是新建立的專案,可以利用前一章所介紹的 Vue CLI 建立專案,並選取 Router 選項:








 






? Check the features needed for your project:
(Press <space> to select, <a> to toggle all, <i> to invert selection)

❯◉ Choose Vue version
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing
1
2
3
4
5
6
7
8
9
10
11
12
13

因為我們選了安裝 Router,所以接下來 Vue CLI 會問你是否採用 history mode,這裏我們先選 Y, 表示採用 HTML5 的 history API 來管理前端路由:

? Use history mode for router? 
(Requires proper server setup for index fallback in production) (Y/n)
1
2

其他部分則是與前面介紹過的一樣。


另外,如果是已經建立好的專案,但在專案建置時並未安裝過 Vue Router 也不用擔心, 只需要打開終端機並切換到專案目錄下,執行:

# 有安裝 Vue CLI 的情況
$ vue add router@next
1
2

接著同樣會詢問你使否採用 history mode

? Use history mode for router? 
(Requires proper server setup for index fallback in production) (Y/n)
1
2

然後讓它跑一會,就安裝完成了。


若讀者朋友並未安裝 Vue CLI,也可透過 npm 或 yarn 來進行安裝:

$ npm install vue-router@4
# 或者 yarn add vue-router@4
1
2

採用 CDN 方式的讀者也可以直接在網頁引入 <script> 標籤:

<scripr src="https://unpkg.com/vue-router@4"></script>
1

這樣就可以了。

# Vue Router 的第一步

安裝了 Vue Router 之後,接著我們需要在網頁加入 <router-view> 來當作 Route 的進入點, 並且使用 <router-link> 來產生對應的連結:

<div id="app">
  <h1>Hello Vue Router App!</h1>

  <p>
    <!-- router-link 在經過編譯後會變成 <a> 標籤 -->
    <!-- 使用 to 屬性來處理目標 URL -->
    <router-link to="/">Go to Home</router-link>
    <router-link to="/about">Go to About</router-link>
  </p>

  <!-- 渲染 route 的位置 -->
  <router-view></router-view>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
// 定義兩個 route 元件,分別是 Home 與 About
const Home = { template: '<div>Home</div>' }
const About = { template: '<div>About</div>' }

// 指定 URL 與對應的元件
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
]

// 建立 VueRouter 實體物件
const router = VueRouter.createRouter({
  history: VueRouter.createWebHistory(),
  routes: routes,
});

// 建立 Vue 實體元件
const app = Vue.createApp({});

// 透過 app.use(router) 使 app 的所有子元件
// 都能存取 this.$router 與 this.$route
app.use(router);

// 掛載 Vue.js 實體至 DOM
app.mount('#app');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

如果是使用 Vue CLI 的讀者,可自行修改 (或新增) route.js 檔案,使用方式大致上於前面相同, 差別只在 routes 元件可以是某個 .vue 檔案,並將其透過 import 的方式引入進來:

// route.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'

export const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About },
  ],
});
1
2
3
4
5
6
7
8
9
10
11
12

接著在 main.js 透過 .use(router) 加入至 app

// main.js
import { createApp } from 'vue';
import App from './App.vue';
import { route } from './route';

const app = createApp(App).use(route);
app.mount('#app');
1
2
3
4
5
6
7

像這樣,渲染在網頁上的結果,就會出現 「Go to Home」 與 「Go to About」 兩個連結, 當點擊其中一個連結之後,畫面並不會被轉導到另一個網頁,而是將對應的元件渲染在 <router-view></router-view> 的區塊當中。

試一試

雖然只是很單純的元件切換,到目前為止我們已經成功使用 Vue Router 建立了一個基本的單頁式應用,那麼在後續的章節中,將為讀者們介紹更多 Vue Router 的相關細節與設定。

Last Updated: 8/2/2021, 2:15:00 PM