# 4-1 Vue Router 與前後端路由
如同前面章節介紹過的,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
└── ...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
舉例來說,上面這是個很常見的網站結構,我們可以透過網誌 https://xxx.xxx/products-list
來進入商品列表頁,甚至再往下一層還有 https://xxx.xxx/category-1
與 https://xxx.xxx/category-1/product-1
進入分類頁以及商品的說明頁。
像這樣,瀏覽器透過不同的網址,向後端的網頁伺服器發送請求 (Request),而伺服器接收到瀏覽器的請求後, 回應對應的內容給瀏覽器來渲染,這樣的機制我們就稱為網站路由 (Routing) ,管理網站路由的程式通常會被稱作 Router。
# 後端路由 vs 前端路由
前面介紹的是傳統後端路由的概念。
而隨著時間發展,前端在整個網頁領域的比例越來越吃重,尤其是自 Ajax 技術問世之後,省下了換頁的成本, 透過 JavaScript 將網頁的內容替換,使用者與網頁畫面的互動不需要重新載入網頁,操作的回饋也更即時。
但同時也遇到了新的挑戰,更新後的內容在網頁重新整理後沒辦法被留存。
過去使用者的習慣,回到前一個步驟通常會去按「上一頁」,但網頁是由 Ajax 更新的資料,根本沒有「換頁」這個行為, 哪裡來的上一頁與下一頁可以給使用者切換。
幸好後來 HTML5 新的規範提供了 History API,可以透過 pushState
、 replaceState
來更新 URL,
同時也提供了 state
物件讓開發者存取網頁的狀態,這就使 JavaScript 開始獲得控制路由的權利。
需要注意的是,若要將網站原本後端伺服器控制路由改為前端接手時, 需要改寫原本的後端路由規則,將所有網址都指向 Vue.js 的單一入口。
以 Vue.js 的好朋友 Laravel 為例,首先要將所有的入口都指向同一個頁面,再由這頁去渲染 Vue.js:
// routes/web.php
Route::get('/{any}', 'SinglePageController@index')->where('any', '.*');
2
接著,在 SinglePageController
將 index
指向 app
:
class SinglePageController extends Controller
{
public function index()
{
return view('app');
}
}
2
3
4
5
6
7
最後記得在 /resources/views/app.blade.php
新增 <div id="app">...</div>
以及
將 Vue.js 以及前端程式的進入點引入到網頁上。<script src="{{ mix('js/app.js') }}"></script>
// 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>
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
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)
2
其他部分則是與前面介紹過的一樣。
另外,如果是已經建立好的專案,但在專案建置時並未安裝過 Vue Router 也不用擔心, 只需要打開終端機並切換到專案目錄下,執行:
# 有安裝 Vue CLI 的情況
$ vue add router@next
2
接著同樣會詢問你使否採用 history mode
:
? Use history mode for router?
(Requires proper server setup for index fallback in production) (Y/n)
2
然後讓它跑一會,就安裝完成了。
若讀者朋友並未安裝 Vue CLI,也可透過 npm 或 yarn 來進行安裝:
$ npm install vue-router@4
# 或者 yarn add vue-router@4
2
採用 CDN 方式的讀者也可以直接在網頁引入 <script>
標籤:
<scripr src="https://unpkg.com/vue-router@4"></script>
這樣就可以了。
# 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>
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');
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 },
],
});
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');
2
3
4
5
6
7
像這樣,渲染在網頁上的結果,就會出現 「Go to Home」 與 「Go to About」 兩個連結,
當點擊其中一個連結之後,畫面並不會被轉導到另一個網頁,而是將對應的元件渲染在 <router-view></router-view>
的區塊當中。
雖然只是很單純的元件切換,到目前為止我們已經成功使用 Vue Router 建立了一個基本的單頁式應用,那麼在後續的章節中,將為讀者們介紹更多 Vue Router 的相關細節與設定。