Vue-router插件使用
單頁面原理
Vue
是單頁面開發,即頁面不刷新。
頁面不刷新,而又要根據用戶選擇完成內容的更新該怎麼做?Vue
中採用錨點來完成。
如訪問//127.0.0.1#/index
就是主頁,而訪問//127.0.0.1#/home
就是家目錄。
手動分析url
組成與處理視圖的切換非常麻煩,所以Vue
提供插件Vue-router
,它能夠根據用戶在地址欄中輸入的不同鏈接展示不同的內容,十分的方便。
Vue-router
打開Vue.js
官網,在生態系統中找到vue-router
。
然後根據它的官方文檔進行安裝即可:
示例演示
使用<router-view>
當作組件的顯示容器。
使用<router-link>
與屬性to
讓Vue
切換顯示的組件。
注意查看下圖中url
的變化,它確實沒有刷新。
<body>
<div id="app">
<router-view></router-view>
</div>
<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
// 第一步:書寫組件
const index = {
template: `
<div>
<h1>歡迎訪問主頁</h1>
<router-link to="/settings">設置</router-link>
<router-link to="/backend">後台</router-link>
</div>
`
}
const backend = {
template: `
<div>
<h1>歡迎訪問後台</h1>
<router-link to="/">訪問主頁</router-link>
</div>
`
}
const settings = {
template: `
<div>
<h1>歡迎訪問個人設置頁面</h1>
<router-link to="/">訪問主頁</router-link>
</div>
`
}
// 第二步:配置路由
// 根據資源請求來展示或隱藏對應的組件
const routes = [
// 主頁,/
{path: "/", component: index},
{path: "/backend", component: backend},
{path: "/settings", component: settings},
]
// 第三步:實例化路由對象
// {routes:routes}
const router = new VueRouter({routes,})
// 第四步:根組件中添加路由資訊
// {router:router}
const app = new Vue({
el: "#app",
router,
})
</script>
</body>
參數相關
參數傳遞
在很多情況下,我們都需要用戶的請求地址攜帶一些參數,然後通過這個參數再進行查詢。
比如查詢某一本書時,地址欄通常是這樣的:
#/book/2
而且很有可能在今後的<router-link>
以及methods
中都要使用這個參數,如何操作?
{path: "/book/:id(\\d+)", component: book}, // 轉義 \d+
我該如何從Template里拿到參數:{{ $route.params }}
我該如何從Methods里拿到參數:this.$route.params
示例如下:
<body>
<div id="app">
<router-view></router-view>
</div>
<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
// 第一步:書寫組件
const index = {
template: `
<div>
<h1>主頁</h1>
<input type="text" v-model="book_id" placeholder="輸入要查詢的圖書編號">
<router-link :to="'/book/'+book_id">查詢圖書</router-link>
</div>
`,
data() {
return {
book_id: 0,
};
}
}
const book = {
// 在模板以及方法中,你都可以拿到查詢的參數
template: `
<div>
<p>我該如何從Template里拿到參數:{{ $route.params }}</p>
<p>我該如何從Methods里拿到參數:{{ show() }}</p>
<p v-for="item in books" v-if="item.id==$route.params.id">你查詢的圖書是:{{ item }}</p>
</div>
`,
data() {
return {
books: [
{id: 1, name: "紅樓夢", author: "曹雪芹", price: 199,},
{id: 2, name: "西遊記", author: "吳承恩", price: 179,},
{id: 3, name: "三國演義", author: "羅貫中", price: 158,},
{id: 4, name: "水滸傳", author: "施耐庵", price: 128,},
]
}
},
methods:{
show(){
return this.$route.params
}
}
}
// 第二步:配置路由
const routes = [
// 主頁,/
{path: "/", component: index},
{path: "/book/:id(\\d+)", component: book}, // 轉義 \d+
]
// 第三步:實例化路由對象
// {routes:routes}
const router = new VueRouter({routes,})
// 第四步:根組件中添加路由資訊
// {router:router}
const app = new Vue({
el: "#app",
router,
})
</script>
</body>
默認參數
根據RestAPI
規範,如果沒有攜帶參數則代表查詢所有。
如果按照上面的示例,直接不帶參數進行請求路由將匹配不上,所以我們還需要做一個查詢所有的功能。
其實對上面路由進行改進即可,設置為默認參數:
{path: "/book/:id?", component: book}, // ?代表可以有也可以沒有
示例如下:
<body>
<div id="app">
<router-view></router-view>
</div>
<!--查詢模板-->
<template id="result">
<div>
<table border="1" :style="{borderCollapse:'collapse'}">
<caption :style="{border:'1px solid #000',fontSize:'1.2rem'}">查詢結果</caption>
<thead>
<tr>
<th>編號</th>
<th>名稱</th>
<th>作者</th>
<th>價格</th>
</tr>
</thead>
<!-- 查詢一本 -->
<tbody v-if="$route.params.id">
<tr v-for="item in books" v-if="item.id == $route.params.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.author }}</td>
<td>{{ item.price }}</td>
</tr>
</tbody>
<!-- 查詢所有 -->
<tbody v-else>
<tr v-for="item in books">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.author }}</td>
<td>{{ item.price }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
// 第一步:書寫組件
const index = {
template: `
<div>
<h1>主頁</h1>
<input type="text" v-model="book_id" placeholder="輸入要查詢的圖書編號">
<router-link :to="'/book/'+book_id">查詢單個圖書</router-link>
<router-link to="/book/">查詢所有圖書</router-link>
</div>
`,
data() {
return {
book_id: 0,
};
}
}
const book = {
// 在模板以及方法中,你都可以拿到查詢的參數
template: `#result`,
data() {
return {
books: [
{id: 1, name: "紅樓夢", author: "曹雪芹", price: 199,},
{id: 2, name: "西遊記", author: "吳承恩", price: 179,},
{id: 3, name: "三國演義", author: "羅貫中", price: 158,},
{id: 4, name: "水滸傳", author: "施耐庵", price: 128,},
]
}
},
}
// 第二步:配置路由
const routes = [
// 主頁,/
{path: "/", component: index},
{path: "/book/:id?", component: book}, // 轉義 \d+,?可以有也可以沒有
]
// 第三步:實例化路由對象
// {routes:routes}
const router = new VueRouter({routes,})
// 第四步:根組件中添加路由資訊
// {router:router}
const app = new Vue({
el: "#app",
router,
})
</script>
</body>
路由別名
路由name
為每一個路由匹配規則添加name
屬性,使其更方便的在模板以及Js
程式碼中進行跳轉:
如下所示:
const routes = [
{path: "/", component: index, name: "index"},
{path: "/book/:id?", component: book, name: "query_book"},
]
router-link
模板中使用router-link
與路由的別名name
進行跳轉時,格式如下:
<!-- 有參數就傳遞,沒有就不傳遞。注意在to前添加: -->
<router-link :to="{name:'query_book',params:{id:書籍編號}}">查詢</router-link>
$route.push
Js
程式碼中使用跳轉時要用到this.$router.push()
這個方法,如下所示:
// 模板中的按鈕
<button @click="func(書籍編號)">查看詳情</button>
// js
func(bookId){
// 使用url拼接跳轉
let url = {path:"/book/"+bookId};
// 使用別名進行跳轉跳轉:
// {name:'book_query',params:{id:id}}
this.$router.push(url); // 使用$router.push(url)進行跳轉
}
視圖布局
視圖嵌套
一個大的組件中可以包含很多小的組件。
如下所示,有一個school
組件,在school
組件中你可以查看到當前的teacher
和classes
。
當然teacher
與classes
也都是兩個組件。
換而言之,我在school
組件中點擊查看教師或者班級,我並不希望他跳轉到新的頁面而是在當前頁面的其他位置進行顯示其他組件就可以使用路由嵌套。
<div id="app">
<!-- 第一層,展示學校 -->
<router-view></router-view>
</div>
# 子組件中的關鍵程式碼
<router-view></router-view>
# 路由中的程式碼,第一層的路由匹配第一層的router-view,第二層的路由就在第二層的router-view中顯示
path: "/school", component: school, name: "school", children: [
{path: "/school/teacher", component: teacher, name: "teacher"},
{path: "/school/classes", component: classes, name: "classes"},
]
<body>
<div id="app">
<!-- 第一層,展示學校 -->
<router-view></router-view>
</div>
<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
// 第一步:書寫組件
const school = {
template: `
<div>
<h1>歡迎來到大肥羊學校</h1>
<router-link :to="{name:'classes'}">查看班級</router-link>
<router-link :to="{name:'teacher'}">查看老師</router-link>
<!-- 第二層,展示班級或者教師 -->
<router-view></router-view>
</div>
`
}
const teacher = {
template: `
<div>
<ul>
<li v-for="item in teacherMessage">{{item.id}}-{{item.name}}</li>
</ul>
</div>
`,
data() {
return {
teacherMessage: [
{id: 1, name: "王老師",},
{id: 2, name: "張老師",},
{id: 3, name: "李老師",},
]
}
}
}
const classes = {
template: `
<div>
<ul>
<li v-for="item in classMessage">{{item.id}}-{{item.name}}</li>
</ul>
</div>
`,
data() {
return {
classMessage: [
{id: 1, name: "一年級一班",},
{id: 2, name: "一年級二班",},
{id: 3, name: "一年級三班",},
]
}
}
}
// 第二步:配置路由
const routes = [
{
path: "/school", component: school, name: "school", children: [
{path: "/school/teacher", component: teacher, name: "teacher"},
{path: "/school/classes", component: classes, name: "classes"},
]
},
]
// 第三步:實例化路由對象
// {routes:routes}
const router = new VueRouter({routes,})
// 第四步:根組件中添加路由資訊
// {router:router}
const app = new Vue({
el: "#app",
router,
})
</script>
</body>
嵌套的問題
當頁面發生變化,如#school/classes
跳轉到#school/teacher
,school
組件將會產生復用。
這代表school
的組件聲明周期鉤子函數不會被重複調用,就可能造成數據更新不及時的問題。
舉一個例子,上述示例中的school
歡迎語是歡迎來到大肥羊學校,如果它是鉤子函數created()
從後端獲取的數據,在用戶查看#school/classes
後跳轉到#school/teacher
這個時間點中間後端數據發生了改變,變成了歡迎來到小肥羊學校,由於組件復用問題不會再次執行created()
,則代表用戶依舊看到的是歡迎來到大肥羊學校。如下所示,我們只有手動更新標語才能執行更新,這顯然是不符合常理的:
<body>
<div id="app">
<router-view></router-view>
</div>
<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
// 假設是從後端抓取數據
let schoolTitle = "歡迎來到大肥羊學校";
// 5s後發生改變
setTimeout(() => {
schoolTitle = "歡迎來到小肥羊學校";
}, 5000);
// 第一步:書寫組件
const school = {
template: `
<div>
<h1>{{ title }}</h1>
<router-link :to="{name:'classes'}">查看班級</router-link>
<router-link :to="{name:'teacher'}">查看老師</router-link>
<p><button @click="updateTitle">更新新的標語</button></p>
<router-view></router-view>
</div>
`,
data(){
return {
title : "",
}
},
created(){
// 假設發送非同步請求
console.log("school鉤子函數觸發了...")
this.title = schoolTitle;
},
methods:{
updateTitle(){
this.title = schoolTitle;
}
}
}
const teacher = {
template: `
<div>
<h3>老師太多了,顯示不過來...</h3>
</div>
`,
}
const classes = {
template: `
<div>
<h3>班級太多了,顯示不過來...</h3>
</div>
`,
}
// 第二步:配置路由
const routes = [
{
path: "/school", component: school, name: "school", children: [
{path: "/school/teacher", component: teacher, name: "teacher"},
{path: "/school/classes", component: classes, name: "classes"},
]
},
]
// 第三步:實例化路由對象
// {routes:routes}
const router = new VueRouter({routes,})
// 第四步:根組件中添加路由資訊
// {router:router}
const app = new Vue({
el: "#app",
router,
})
</script>
</body>
解決問題
如果想解決組件復用鉤子函數不執行的問題,我們可以使用watch
來監聽$route
對象,也就是使用watch
來監聽地址欄變化,當發生變化時就重新獲取數據。
或者使用 2.2 中引入的 beforeRouteUpdate
導航守衛,解決思路如下圖所示:
程式碼如下,使用watch
進行解決:
const school = {
template:"...",
data(){
return {
title : "",
}
},
created(){
// 假設發送非同步請求
this.getTitle();
},
methods:{
getTitle(){
// 從後端獲取數據
this.title = schoolTitle;
}
},
watch:{
$route(to,from){
// to 要跳轉的頁面
// from 從那個頁面進行跳轉
this.getTitle();
}
}
}
使用導航守衛進行解決的程式碼如下:
// 第一步:書寫組件
const school = {
template:"...",
data(){
return {
title : "",
}
},
created(){
// 假設發送非同步請求
this.getTitle();
},
methods:{
getTitle(){
// 從後端獲取數據
this.title = schoolTitle;
}
},
beforeRouteUpdate (to, from, next) {
this.getTitle();
}
}
命名視圖
命名視圖就是說可以在一個頁面上,使用多個<router-view>
,相較於路由嵌套的層級關係,它是一種扁平化的設計。
如,頭部導航欄,左側菜單欄,右邊內容塊三個組件,都顯示在一個頁面上,就可以使用命名視圖。
核心程式碼如下:
# 根組件模板
<div id="app">
<!-- 這裡只放個人主頁 -->
<router-view></router-view>
<router-view name="menu"></router-view>
<router-view name="show"></router-view>
</div>
# Js配置路由,/代表根目錄。有三個視圖,router-view
path: "/", components: {
default: header, // 如果 view-router沒有name屬性,則用default
menu: menu,
show: show,
}
<style>
*{
padding: 0;
margin: 0;
box-sizing: border-box;
}
header{
height: 45px;
display: flex;
justify-content: space-evenly;
background-color: #ddd;
align-items: center;
}
body>div>div:nth-child(2){
display: inline-flex;
width: 15%;
border: 1px solid #ddd;
height: 1000px;
}
menu>ul{
list-style-type: none;
display: inline-flex;
flex-flow: column;
}
menu>ul>li{
margin: 10px 0 0 10px;
}
body>div>div:last-child{
display: inline-flex;
justify-content: center;
border: 1px solid #ddd;
width: 70%;
height: 1000px;
}
</style>
<body>
<div id="app">
<!-- 這裡只放個人主頁 -->
<router-view></router-view>
<router-view name="menu"></router-view>
<router-view name="show"></router-view>
</div>
<!-- 頭部組件 -->
<template id="header">
<div>
<header><span>首頁</span><span>新聞</span><span>關注</span><span>鏈接</span></header>
</div>
</template>
<!-- 左側菜單 -->
<template id="menu">
<div>
<menu>
<ul>
<li>最新</li>
<li>最熱</li>
<li>最多評論</li>
</ul>
</menu>
</div>
</template>
<!-- 內容區域 -->
<template id="show">
<div>
<section>
<h1>內容區域</h1>
</section>
</div>
</template>
<script src="vue.js"></script>
<script src="vue-router.js"></script>
<script>
// 第一步:書寫組件
const header = {
template: "#header",
}
const menu = {
template: "#menu",
}
const show = {
template: "#show",
}
// 第二步:配置路由
const routes = [
{
// 當你訪問主頁時,有三個組件扁平顯示
path: "/", components: {
default: header, // 如果 view-router沒有name屬性,則用default
menu: menu,
show: show,
}
}
]
// 第三步:實例化路由對象
const router = new VueRouter({
routes, // es6新語法
})
// 第四步:根組件中添加路由資訊
const app = new Vue({
el: "#app",
router,
});
</script>
</body>
重定向
redirect
當你訪問一個頁面時,可以重定向至另一個頁面,如下示例,使用redirect
進行重定向。
訪問/doc
,重定向到/help
中。但是地址欄中顯示的還是/help
:
const routes = [
// 當用戶訪問doc時,將會跳轉到help中,地址欄中顯示是help
{ path: "/help", component: help, name: "help"},
{ path: "/doc", redirect: { name: "help" } }
]
alias
如果你使用alias
參數進行匹配,就方便許多了,並且地址欄中顯示的是用戶輸入的值,但是當輸入的路徑不存在,則不會顯示:
const routes = [
// 用戶輸入在alias中的所有路徑,都會交給help組件進行處理
{ path: "/help", component: help, name: "help", alias: ["/doc", "/doc.html", "/help.html"] },
]
history模式
如果你的url
中不想有#
號的錨點,可開啟history模式。
同時你還需要在後端做相應的配置,參見官方文檔:
切換動畫
相信現在你已經對單頁面開發有所了解,單頁面開發說白了就是根據url
請求的#
後的參數不停的更換要顯示的組件。
所以我們可以為這一切換過程加上過渡動畫,你可以在其他子組件模板中添加<transition>
標籤,並自己書寫css
類或者引用第三方庫。
如下所示:
我這裡是單獨給每個子組件加的動畫:
// 書寫組件
const index = {
template:
`
<transition enter-active-class="animate__animated animate__bounce">
<h1>wecome to index</h1>
</transition>
`,
}
const backend = {
template: `
<transition enter-active-class="animate__animated animate__bounce">
<h1>wecome to backend</h1>
</transition>
`,
}
如想了解更多,請參考官方文檔。