上篇完成了巢狀路由的設置之後,緊接著新需求又出現了!接續會員後台的收藏紀錄頁面,我們要進一步讓收藏的書單可以被購買:
在解決需求之前,我們先來認識一下動態路由要怎麼設定。
:
」動態比對路由參數先前我們對於路由的設定都是連名帶姓的指定路徑內容,就像是詳細提供你家地址,好讓任何人可以直接親門踏戶的找到你家。
然而有些情況下我們並無法完全預測到路徑的走向,常見的情境例如要對應出 user name、product id 或是 query 查詢內容,這些都是隨機資料,無法在一開始就指明清楚;不過我們還是可以憑藉著「關鍵字」先概括出方向,再帶入符合的內容作為路徑參數。
以會員資料為例,我們可以參考 GitHub 在 profile 頁面所顯示的網址內容「 https://github.com/帳戶名稱 」,每個人登入後所看的的網址都會像是被客製化一樣顯示出自己的帳戶名稱,太神奇了吧!它怎麼知道現在是誰登入網頁?
這就可以利用「:
」來達成動態比對路由,甚至不需要再設定多層巢狀路由,例如:
const router = new VueRouter({
routes: [
{
path: 'member/:userName',
component: member
}
]
})
通常會先將 userName
(需為唯一值,否則會找到多位同名會員)作為 request 參數發送一支搜尋會員的 API,確認有該名會員時再導向包含 userName
片段的會員路徑,因此路徑可以是「member/mary」,又或者是「member/john」。當路由完成匹配時,userName
片段就會被設置為路由物件屬性 $route.params
的參數值之一,因此還可以直接利用該屬性在所有元件中的 <template> 顯示出用戶名稱:
<p>User Name: {{ $route.params.userName }}</p>
?
」非強制比對路由參數若某個路由參數對你而言是個可有可無的狀態,則可以使用「?
」進行非強制比對。
使用問號:設置可以匹配「member/001」,也可以是「member/001/mary」,雖然導向的路徑不同,但兩者顯示的都是同一個元件內容,因此無論 userName
是否存在都不會影響連結結果。
path: 'member/:userId/:userName?',
使用冒號:設置必須完整匹配「member/001/mary」,若連至「member/001」則無法成功顯示頁面。
path: 'member/:userId/:userName',
了解以上動態路由的設置方法之後,我們就可以馬上應用來解決新需求!
首先,新增 Purchase.vue 元件,並設置動態路由。
router/index.js
{
path: "/member",
redirect: { name: "Profile" },
component: MemberPage,
children: [
{
path: "profile",
name: "Profile",
component: Profile,
},
{
path: "collection",
name: "Collection",
component: Collection,
},
{
path: "purchase/:bookName",
name: "Purchase",
component: Purchase,
},
],
},
接著在 Collection.vue 新增購買按鈕或是導向連結,點擊後觸發路由導向 Purchase 元件,並且比對路由參數 bookName
。
方法一:使用 Bootstrap 元件 <b-button> 建立購買按鈕
<ul>
<li v-for="book in collections" :key="book.id">
<b-button
variant="outline-success"
@click="$router.push({ name: 'Purchase', params: { bookName: book.name } })"
>
Buy
</b-button>
<p>Book Name: <span>{{ book.name }}</span></p>
</li>
</ul>
方法二:使用 <router-link> 建立導向連結
<ul>
<li v-for="book in collections" :key="book.id">
<router-link
class="buy_button"
:to="{ name: 'Purchase', params: { bookName: book.name } }"
>
Buy
</router-link>
<p>Book Name: <span>{{ book.name }}</span></p>
</li>
</ul>
由於 Collection.vue 觸發路由導向時,已將書本名稱指定為路徑需帶上的 params 參數,而 Purchase.vue 在設置路由時,也透過「:
」進行動態路由比對需匹配 bookName
才是有效路徑,因此在 Purchase.vue 可以直接利用路由參數 $route.params
渲染出書本名稱。
<div class="Purchase">
<h1>Purchase</h1>
<div class="bill">
<p>
Name: <span>{{ $route.params.bookName }}</span>
</p>
<p>Quantity: <span>1</span></p>
<p>Price: <span>$500</span></p>
</div>
</div>
試著點擊任何一本書目的購買連結,都能依照不同的路徑切換而即時更新書本名稱;例如切換到第二本書,當前路徑為「/member/collection/purchase/Book%202」,其中網址內的「%20」為原本書名「Book 2」中代表空白格的編碼。
目前每個購買頁面確實都有各自的獨立連結了,並且因為購買頁面也是隸屬於 MemberPage 元件之下的子層巢狀路由,所以即使沒有另外新增在會員導覽項目中,仍有共享到會員導覽列。
不過還差那麼一點,由於購買流程是從收藏紀錄延伸出去的動線,因此新需求中還需要符合讓購買頁面的底部裝飾線與收藏紀錄共用,相當於進入購買頁面之後,裝飾線仍停留在收藏紀錄上,然而目前從畫面中我們並無從得知使用者的當前位址。
作法很簡單,原本 Purchase 路徑為「path: "purchase/:bookName"
」,只要再加上一段字串:
{
path: "/member",
redirect: { name: "Profile" },
component: MemberPage,
children: [
...
{
path: "collection",
name: "Collection",
component: Collection,
},
{
path: "collection/purchase/:bookName",
name: "Purchase",
component: Purchase,
},
],
},
在前篇曾介紹過 <router-link>
props 中包含 exact-active-class 及 active-class,此處正是利用當初在 MemberPage 元件中直接取用 .router-link-active
設定裝飾線樣式,它會透過模糊比對為當前匹配的路由時加上該 class,因爲「collection/purchase/:bookName」在模糊比對之下與「collection」路徑相似而符合資格,所以在 Purchase 路徑之下會跟著顯示裝飾線樣式。
此時若在 MemberPage 元件改用.router-link-exact-active
,Purchase 就會因為路徑不符合精準比對的結果,而無法渲染出相應樣式。