【Vue.js 學習筆記】10. Vue Router

本節內容包含下述子章節:

  1. 使用 Vue Router 及配置路由文件
  2. 新增路由路徑及連結
  3. 製作巢狀路由頁面
  4. 使用動態路由切換頁面 Ajax 結果
  5. 命名路由,同一個路徑載入兩個頁面元件
  6. Vue Router 參數設定
  7. 自定義切換路由方法


使用 Vue Router 及配置路由文件

Vue Router 是由前端所模擬的網頁路由技術,可以讓使用者透過網址,決定要顯示的頁面。而非傳統網頁,一個網址就是一個頁面。使用時所輸入網址內容,會在前端程式判定所輸入的條件轉譯成搜尋條件,返回相對應的網頁。


安裝與配置流程

  1. 事前準備
    請先閱讀參考文件

  2. Vue Router
    在專案下輸入安裝指令
    1
     $ npm install vue-router --save
    

    指令最後的 -save 或是 save-dev 是指自動將把套件稱和版本號添加到 package.json 文件中 dependencies / devdependencies 部分。

    一般來說,為專案添加套件時必須先進行安裝,即在專案下輸入安裝指令,然後連同版本號手動將他們添加到配置文件 package.json 中的 dependencies 裡

    1
     $ npm install vue-router
    

    若在指令中添加 -save 或是 save-dev 可以省掉你手動修改的 package.json 文件的步驟。

  3. 路由的配置
    添加 src/router/index.js,當作前端路由的配置檔案,決定哪個網址讀取哪份檔案。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 引入官方元件
    import Vue from 'vue'
    import VueRouter from 'vue-router'  
    
    // 引入自定義元件
    import Hello from '@/components/HelloWorld.vue'   
    
    // 啟用 VueRouter 元件
    Vue.use(VueRouter)
    
    // 匯出給 entry 使用
    export default new  VueRouter({  
    
    });
    


  4. 引入Vue Router
    entry(src/main.js)匯入 Vue Router,並在 Vue 元件中引入該物件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     import Vue from 'vue'
     import App from './App'
     import router from "./router" 
    	 
     new Vue({
        el: '#app',
        components: { App },
        template: '<App/>',
     router,
     })
    


    我自己在練習的時候在這步驟卡好久,一直得到下面 error

    Imgur


    找了一陣子才發現,我一開始寫的時候是用

    1
     import Router from "./router" 
    

    只要把它改成

    1
     import router from "./router"
    

    下面 Vue 元件中引入該物件的地方也把 Router 換成 router 就好了。

    就是不曉得為什麼這樣就 work 了?

  5. 定義路徑
    回到 src/router/index.js 定義路徑,在 VueRouter 元件中新增一個名為 routes陣列 ,裡面包含數的定義好的路徑物件。
    1
    2
    3
    4
    5
    6
    7
    8
     // 匯出給 entry 使用
     export  default  new  VueRouter({
        routes:[{
           name:"HomePage", // 元件呈現的名稱
           path:"/Hello", // 對應的虛擬路徑
           component:  Hello  // 對應的元件
        }],
     });
    


  6. 加上 router-view
    回到 App.vue 中更改 template,在 template 中加上 router-view 的標籤(可以順便 mark 掉原先的 HelloWorld 標籤)
    1
    2
    3
    4
    5
     <template>
        <div id="app">
           <router-view></router-view>
        </div>
     </template>
    
  7. 測試
    在網址中加上 Hello,就可連到所定義的元件。



新增路由路徑及連結

這邊會試著加入一個分頁,並新增一個導覽列來切換兩個分頁

  1. 引入 Bootstrap
    在開始之前,先在 index.html 的 header 中引入 Bootstrap
    1
    2
    3
     <link  rel="stylesheet"  href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    
     integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"  crossorigin="anonymous">
    
    注意
    是 Bootstrap 不是 Bootstrap Vue
    是 Bootstrap 不是 Bootstrap Vue
    是 Bootstrap 不是 Bootstrap Vue


  2. 新增頁面
    在 components 的資料夾中新增一個新的元件命名為 page.vue,並將它與 HelloWorld 元件做出差異。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     <template>
       <div class="hello">
           <div class="card"   style="width: 18rem;">
             <div class="card-body">
                 <h5 class="card-title">Card title</h5>
                 <p class="card-text">example.</p>
                 <a href="#" class="btn btn-primary">Go somewhere</a>
             </div>
           </div>
       </div>
     </template> 
    
     <script>
     export default {
       data () {
           return {
           }
       }
     }
     </script>
    


    這邊用 Bootstrap 的 card 元件做 page.vue,可以直接照範例貼上就好。如果不想這麼麻煩,直接在 template 裡面塞一段文字也行,分得出 HelloWorld 與 Page 就可以了。

  3. 為新頁面配置網址
    src/router/index.js 中添加新增 page.vue
    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
    26
     // 引入官方元件
     import Vue from 'vue'
     import VueRouter from 'vue-router'
    
     // 引入自定義元件
     import HelloWorld from '@/components/HelloWorld.vue'
     import Page from '@/components/page/page.vue'
    
     // 啟用 VueRouter 元件
     Vue.use(VueRouter) 
    
     // 匯出給 entry 使用
     export default new VueRouter({
       routes: [
           {
             name: "HelloWorld", // 元件呈現的名稱
             path: "/Hello", // 對應的虛擬路徑
             component: HelloWorld   // 對應的元件
           },
           {
             name: "Page",
             path: "/Page",
             component: Page
           }
       ],
     });
    


    加上後可以試著在 url 上分別加上 /Hello 或 /Page 確定是否能夠順列切換。

    PS. page.vue 是放在 components 下一個名為 page資料夾裡面,所以引入路徑中寫成 @/components/page/page.vue

  4. 新增個頁簽
    在 App.vue 中加上 Bootstrap 的 Navbar,並新增兩個頁簽。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     <template>
       <div id="app">
         <nav class="navbar navbar-expand-lg navbar-light bg-light">
           <a class="navbar-brand" href="#">Navbar</a>
           <div class="collapse navbar-collapse"  id="navbarSupportedContent">
             <ul class="navbar-nav mr-auto">
               <li class="nav-item">
                 <router-link class="nav-link" :to="{name:'HelloWorld'}">Hello</router-link  >
               </li>
               <li class="nav-item">
                 <router-link class="nav-link"  to="/Page">Page</router-link>
               </li>
             </ul>
           </div>
         </nav>
         <img src="./assets/logo.png">
         <router-view></router-view>
       </div>
     </template>
    

    其中程式碼中 router-link 是切換簽斷路由的標籤,可以直接給定定義好的完整 url

    1
     <router-view to="/Page"></router-view>
    

    或者給定定義的命名好的路由物件,注意傳物件的時候要加上 v-bind。

    1
     <router-view:to="{name:'HelloWorld'}"></router-view>
    


    除了聲明式外,也可在程式中使用 router.push 方法 進行跳轉。

    1
    2
    3
    4
    5
     this.$router.push('/Page');
        
      
        
     this.$router.push('{name:"HelloWorld"}');
    


    比起輸入完整的 url,個人更喜歡傳送路由物件,我覺得可讀性更高,程式維護起來也比較容易,尤其當你還需要傳遞 params 與 query 時,還可以避免做字串串接。

    PS. 是說如果覺得 Navbar 不貼頂很奇怪,可以修改下面的 style,把 margin-top 移除掉。



製作巢狀路由頁面

巢狀路由(嵌套路由)示意圖

巢狀路由(嵌套路由)示意圖 (圖片來源: Vue Router 官網


巢狀路由比較適合用在,只變換主體內容的元件其餘元件(如上方 Navbar)不變的情況。這樣可以專心撰寫主體內容的元件,並大幅減少重複其餘元件的重複撰寫。

  1. 實作個頁面
    試著在剛剛製作出來 page.vue 中加上 <router-view> ,並新增另外三個子原元件。
    • page.vue
      1
      2
      3
      4
      5
      6
      7
        <template>
          <div class="hello">
            <div class="card" style="width: 18rem;">
             <router-view></router-view>
            </div>
          </div>
        </template>
      
    • card_X.vue

      1
      2
      3
      4
      5
      6
      7
      8
      9
        <template>
          <div>
            <div class="card-body">
              <h5 class="card-title">Card X</h5>
              <p class="card-text">Card X.</p>
              <a href="#" class="btn btn-primary">Go somewhere</a>
            </div>
          </div>
        </template>
      


      這邊為了做出來漂亮,因此將 card 中的 card-body 抽出來放到各個子元件中,如果偷懶的話,其實可以不再抽,把 <router-view> 放在卡片下方,子元件中隨便塞一行字就好,你自己分的出來有切換就好。

  2. 設置路由
    src/router/index.js 中添加新增這幾個元件,並在要配置巢狀路由元件中使用 children 配置,

    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
    26
    27
     import Card1 from '@/components/page/card1.vue'
     import Card2 from '@/components/page/card2.vue'
     import Card3 from '@/components/page/card3.vue' 
    
    
     {
       name: "Page",
       path: "/Page",
       component: Page ,
       children:[
       {
         name: "card1",
         path: "", // path 為空,父元件預設帶入
         component: Card1
       },
       {
         name: "card2",
         path: "card2",
         component: Card2
       },
       {
         name: "card3",
         path: "card3",
         component: Card3
        },
      ]
     }
    


    注意在設定子元件的路徑時,前方不需要再加入 / 了。若 children 的路徑為空,代表父元件預設為帶入這個子元件。各個元件詳細路徑如下

    • Card1:/Page
    • Card2:/Page/card2
    • Card3:/Page/card3



使用動態路由切換頁面 Ajax 結果

一種滿常見的應用情境,尤其在如商品、新聞…等,大量使用相同版型的應用場景中最常使用。在使用時會設定特定的匹配模式,將符合該匹配模式的路由,全都導向特定同個元件。


博客來商品頁

博客來商品頁 (圖片來源: 博客來


以博客來為例,在商品介紹頁中皆使用了相同版型,並仔細觀察相對應的網址,可以發現網址皆為 https://www.books.com.tw/products/商品ID 的樣式。

在 Vue 中這樣的情況可以採用動態路由來實做,將符合 products/:id 這樣的路由,全導向特定商品介紹的元件,而實際載入的商品資料由 id 來控制。


這邊以使個人資料卡為例

  1. 安裝 vue-axios
    開始前先安裝 vue-axios,這是來進行 ajax 請求的套件,主要是為取得一些隨機個人資料
    1
     $ npm install --save axios vue-axios
    


    安裝完成後回到 entry(src/main.js)匯入 vue-axios

    1
    2
    3
     import  axios  from  'axios'
     import  VueAxios  from  'vue-axios'
     Vue.use(VueAxios, axios)
    


  2. 設定動態路由
    src/router/index.js 中去設定動態路由匹配模式。使用以冒號開頭動態路徑參數,例如:
    1
      { path: '/user/:id', component: Card3 }
    

    去宣告一個路徑參數,當 url 匹配到一個路由時,這個參數值會被設置到 this.$route.params


  3. 實作資料讀取

    回到卡片元件中,使用 vue-axios 去讀取 user 的資料。

    1
    2
    3
    4
    5
    6
    7
    8
     created(){
       const id = this.$route.params.id;
       this.$http.get(`https://randomuser.me/api/?seed=${id}`).then((res)=>{
         const personalData = res.data.results[0];
         this.name = personalData.name;
         ...
       })
     },
    


    https://randomuser.me/api/?seed=${id} 這是由 randomuser 所提供的 API,給定一個 seed 後,會回傳特定使用者資料,而所送出的 seed 是取自 this.$route.params.id,也就剛剛所設定的動態路徑參數。
    this.$http.get 是一個非同步的動作,最終取得一個 Promise ,必須再呼叫 then 將個人資料取出放入變數,最終將變數在 UI 中呈現。

  4. 測試
    實做完成後可以使用 Page/card/:id 來切換不同的 user 資料。



命名路由,同一個路徑載入兩個頁面元件

website structure

website structure (圖片來源: wikimedia


如果時候想同時(同級)展示多個元件,例如建置一個 layout 時,會存在 sidebar 、 header 、 footer …等元件,這時可以使用命名路由來載入,即在 layout 中添加命名 router-view。

一般來說,如果router-view沒有設置名字,那麼預設為 default

1
2
3
4
5
6
7
8
<template>
  <div class="hello">
    <router-view name="menu"></router-view>
    <div class="card" style="width: 18rem;">
      <router-view></router-view>
    </div>
  </div>
</template>


並在 router 設定相對定的設置。一個路由若要選染多個元件,並需改用 components(有 s)

1
2
3
4
5
6
7
8
9
{
  name: "Page",
  path: "/Page",
  components: {
    default: Page,
    menu: menuComponent,
  },
  children:[...]
}



Vue Router 參數設定

自己看文件



自定義切換路由方法

除了前面課程所教過使用聲明式 router-link 進行路由切換外,也可以使用文件中提供的其他方法進行跳轉。

需注意的是,在實務中要使用文件所提到 method ,如:router.push,需加上前綴變成 this.$router.push 才能成功調用。


課堂發問區有人提問:請問老師為什麼我們的router前面還要多補一個$呢?

講師回覆:因為我們是在 CLI 內運行
前方有先執行了 vue.use(….)

因此外部資源會被掛載到 vue 內
就可以使用 this 來呼叫這些資源(包含 router 及接下來的 axios 都是這個概念)



文件中常用的 method 如下:

  • router.push
    跳轉到指定路由,並將此動作加到跳轉紀錄中。如果使用 stack 來想像這個行為,就是一個 Push into the history stack 的概念。

  • router.back
    回到上一頁。可想像成是一個 pop from history stack,另外這邊的 pop 方法可以想像成是使用移動 top 標籤的那種實做方式。

  • router.go
    回到指定動作。可想像成是一個 top 的移動到指定位置去讀 history stack。

  • router.forward
    回到下一頁。可想像成是一個把 top 標籤加 1 後讀取 history stack 。

  • router.replace
    跟 router.push 類似一樣會跳轉到到指定路由,但會取代掉最上層的紀錄。可想像成他先 pop history stack 最上層的紀錄後,在 Push 新的紀錄到 the history stack 。

PS. stack 中用到的名稱可以參考 Stack: 以Array與Linked list實作



其他設定檔

還會出現如:.babelrc 、 .postcssrc …等設定檔。



其他連結

  1. 【Vue.js 學習筆記】00. 目錄



參考資料

  1. 六角學院-Vue 出一個電商網站|Udemy
  2. Vue router 實戰|《Chris 技術筆記》
  3. node.js - npm 安装参数中的 –save-dev 是什么意思|SegmentFault 思否
  4. Stack: 以Array與Linked list實作|alrightchiu