點燈坊

失くすものさえない今が強くなるチャンスよ

Vue Router 之 Dynamic Route Matching

Sam Xiao's Avatar 2019-06-17

在實務上 Route 也會如 Restful API 一樣,動態在 Route 中夾帶 Data,此時可使用 Dynamic Route Matching,而不用將 Route 寫死。

Version

macOS Mojave 10.14.5
Node 12.4.0
Vue CLI 3.8.4
Vue 2.6.10
Vue-router 3.0.3

Params

dynamic000

  1. 使用 products/1 在網址傳入 1
  2. 在 HTML 也會動態顯示 T-Shirts

router.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router);

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    },
    {
      path: '/products/:id',
      name: 'products',
      component: () => import(/* webpackChunkName: "products" */ './views/Products.vue')

    }
  ]
});

21 行

{
  path: '/products/:id',
  name: 'products',
  component: () => import(/* webpackChunkName: "products" */ './views/Products.vue')
}

path 加上 /products/:id,其中 id 為 param,前面加上 :

Products.vue

<template>
  <div>
    <h1>Products</h1>
    <h2>{{ product }}</h2>
  </div>
</template>

<script>
let products = {
  0: 'Shoes',
  1: 'T-Shirts',
  2: 'Pants',
};

let product = function() {
  return products[this.$route.params.id] || 'N/A';
};

export default {
  name: 'Products',
  computed: {
    product
  },
};
</script>

第 4 行

<h2>{{ product }}</h2>

顯示 product computed。

第 9 行

let products = {
  0: 'Shoes',
  1: 'T-Shirts',
  2: 'Pants',
};

宣告 products object,把 products object 當成 map 用。

注意 products 並沒有寫在 data 內,因為此為 computed function 所使用資料,而非 HTML template 所使用,故不必寫在 data

15 行

let product = function() {
  return products[this.$route.params.id] || 'N/A';
};

使用 this.$route.params,之後加上在 router.js 定義的 id

若對應不到 products 內資料,會回傳 undefined,視為 falsy value,將繼續執行 || 右側的 N/A

因為 product() computed 使用 this context 的 $route,所以只能使用 function expression

App.vue

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/products/1">Product</router-link>
    </div>
    <router-view/>
  </div>
</template>

第 6 行

<router-link to="/products/1">Product</router-link>

<router-link>to 也可以使用 /products/1

Query String

dynamic001

  1. 使用 products?id=1 在網址傳入 2
  2. 在 HTML 也會動態顯示 Pants

route.js

import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
    },
    {
      path: '/products',
      name: 'products',
      component: () => import(/* webpackChunkName: "products" */ './views/Products.vue'),
    },
  ],
});

20 行

 path: '/products',

path/:id 拿掉。

Products.vue

<template>
  <div>
    <h1>Products</h1>
    <h2>{{ product }}</h2>
  </div>
</template>

<script>
let products = {
  0: 'Shoes',
  1: 'T-Shirts',
  2: 'Pants',
};

let product = function() {
  return products[this.$route.query.id] || 'N/A';
};

export default {
  name: 'Products',
  computed: {
    product
  },
};
</script>

15 行

let product = function() {
  return products[this.$route.query.id] || 'N/A';
};

使用 this.$route.query,加上要抓到的 id

App.vue

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/products?id=2">Product</router-link>
    </div>
    <router-view/>
  </div>
</template>

第 6 行

<router-link to="/products?id=2">Product</router-link>

<router-link>to 也可以使用 /products?id=2

Optional Params

dynamic002

若 Params 可有可無,則可使用 ?

即使沒有提供 param,也可以正常顯示 component。

route.js

import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
    },
    {
      path: '/products/:id?',
      name: 'products',
      component: () => import(/* webpackChunkName: "products" */ './views/Products.vue'),
    },
  ],
});

19 行

{
  path: '/products/:id?',
  name: 'products',
  component: () => import(/* webpackChunkName: "products" */ './views/Products.vue'),
},

:id 後面加上 ?,表示 id 為 optional,若沒提供 /1,也會切到 Product component,只是抓不到 product 顯示 N/A 而已。

Matching Priority

dynamic003

Route 明明是 products,卻顯示 About ??

Route 在 match 時,是依照程式碼順序做 match,也就是 先執行到先贏,而不是如 CSS 的 後蓋前

route.js

import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/:page',
      name: 'about',
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
    },
    {
      path: '/products/:id?',
      name: 'products',
      component: () => import(/* webpackChunkName: "products" */ './views/Products.vue'),
    },
  ],
});

15 行

{
  path: '/:page',
  name: 'about',
  component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
},

About component 的 route 改成 /:page

由於 Product.vue 的 route 為 /products/:id?,因此 /products 會被視為 /:pagepageproduct,而採用 /:page route,永遠執行不到 /product/:id ,這就是 matching priority。

Route 在設計時要自行考慮 matching priority

Advanced Pattern

若你要更複雜的 route mathing 機制,Vue 官網建議參考 path-to-regexp,因為 Vue Router 底層就是使用 path-to-regexp 實作,因此可使用 path-to-regexp 提供的參數與格式。

Conclusion

  • Dynamic route matching 讓我們不用將 route 寫死,只要符合特定格式,就可以搭配特定 component
  • 更複雜的 route matching,還可搭配 regular expression

Sample Code

完整範例可在我的 GitHub 上找到

Reference

Vue Router, Dynamic Route Matching