vue-router是Vue的路由管理器,它是Vue的核心插件。在當前Vue項目中一般都是單頁面應用,所以可以說vue-router它是應用在單頁面中的。在web開發中,路由是指根據url分配到對應的處理程序,vue-router它通過對url的管理,實現url和組件的對應,以及通過url進行組件之間的切換。
那什么又是單頁面應用(SPA)?一個項目中只有一個完整的html主頁面,其它都是html片斷組成的分頁面。瀏覽器一開始會在主頁面加載所有必須的文件,即第一次進入頁面的時候會請求一個html文件,在頁面跳轉交互時由路由將分頁面載入到主頁面,這時路徑也相應的發生變化,頁面內容也發生變化,但是並沒有新的html請求。js感知到url的變化后動態的將當前頁面的內容清除然后將下一個頁面的內容掛載到當前頁面上。這時前端就要判斷到底是哪個頁面進行顯示,哪個頁面進行清除。這個過程就是單頁面應用。與之相對的是多頁面應用(MPA),即一個項目是由多個完整的html頁面組成,不存在在主分頁面的說話,每一次頁面跳轉所有的資源都要重新進行加載,后台服務器都會返回一個新的html文檔。就像傳統的頁面應用,它們是用超鏈接來實現頁面切換和跳轉的。
vue-router它實現的原理就是更新視圖但不重新請求頁面。下面我們來介紹vue-router的詳細用法。
一、安裝及使用
1、直接下載:https://unpkg.com/vue-router@3.3.4/dist/vue-router.js & npm: npm install vue-router
2、在index.js中引入vue和vue-router還有頁面組件
import Vue from 'vue' import VueRouter from 'vue-router' import First from '../components/First.vue'
3、安裝Vue.use()插件。 Vue全局使用Router,用來擴展插件。
Vue.use(對象或者函數) 如果是對象會默認調用對象中的install;如果是函數則把這個函數當成install來執行。它應該在調用 new Vue()啟動前完成
Vue.use(VueRouter)
4、創建路由對象配置規則。path:路徑 name: 路徑對應的名字 component: 組件
const routes = [ { path: '/', name: 'First', component: First }, { path: '/second', name: 'Second', //路由懶加載:只要組件不顯示就不去加載對應的組件
component: () => import( '../components/Second.vue') } ]
5、 創建一個VueRouter,然導出。
const router = new VueRouter({ mode: 'history', //它是可以自己配置的。基礎路徑
base: process.env.BASE_URL, //routes:routes 路由映射表 是一個數組
routes, }) //導出
export default router
6、將路由對象傳遞給vue實例。options中加入router:router(可簡寫成router)
new Vue({ router, render: h => h(App) }).$mount('#app')
7、在App.vue中留坑。router-link是router提供的全局組件,是用來提供按鈕展示。router-view它也是router提供的全局組件,是用展示對應的組件。
<!-- <router-link> 默認會被渲染成一個 `<a>` 標簽 -->
<!-- 通過傳入 `to` 屬性指定鏈接-->
<router-link to="/">First</router-link> |
<!-- 還可以用到:to -->
<!-- <router-link :to="{path:'/'}">First</router-link> -->
<!-- :to后面還可以接name -->
<!-- <router-link :to="{name:'first'}">First</router-link> -->
<router-link to="/second">Second</router-link>
<!-- 路由出口 -->
<!-- 路由匹配到的組件將渲染在這里 -->
<router-view></router-view>
二、路由模式
vue-router為了實現單頁面路由跳轉提供了二種模式:hash模式,history模式
hash模式:它是vue-router的默認模式,使用了url的hash來模擬一個完整的url,當url改變時,頁面不會去重新的加載。
hash即#它是url的錨點,代表的是網頁中的一個位置,如果只是改變#后面的部分,瀏覽器只會加載相應的內容不會重新載入網頁。也就是說,#它是用來指導瀏覽器動作的,對服務器完全無用,http請求中不包含#,每一次改變#后的部分都會在瀏覽器的訪問歷史中增加一個記錄,使用“后退”按鈕就能回到上一個位置
hash模式它通過錨點值的改變,根據不同的值,渲染指定的DOM位置上不同的數據。
history模式:由於hash模式會在url中自帶#,影響美觀,這時我們可以用路由的history模式。這種模塊利用了history.pushState API來完成url跳轉而不用重新加載頁面的
// main.js
const router = new VueRouter({ mode:'history', routes:[......] })
當使用history模式時,url就像正常的url一樣。如:http://localhost:8082/second.但這個需要后台配置支持。
進行配置:要使用history模式需要服務器配置。要在服務端增加一個覆蓋所有情況的資源,如果url匹配不到任何靜態資源,則要返回一個html頁面。下面就設置了如果url輸入錯誤或者是url匹配不到任何資源就返回Home頁面。
export const routes = [ {path: "/", name: "homeLink", component:Home} {path: "/register", name: "registerLink", component: Register}, {path: "/login", name: "loginLink", component: Login}, {path: "*", redirect: "/"}]
上面的path: "*", redirect: "/"。使用了重定向功能。重定是通過routes配置來完成的。重定向的目標可以是一個文件;也可以是一個路由;甚至是一個方法,動態返回重定向目標。
const router = new VueRouter({ routers:[ // 從/a重寫向到/b
{ path:'/a',redirect:'/b'}, //它也可以是一個命名的路由
{ path:'/a' ,redirect:{name:'First'} }, //可以是一個方法
{ path:'/a',redirect:to=>{ //方法接收目標路由作為參數 // retrun 路徑+對象
return './first' } } ] })
與重定向redirect相關的還有一個別名alias。使用alias也可以達到redirect的效果。重定指的是當用戶訪問/a時url將會被替換成/b,然后匹配路由/b。而別名是/a的別名是/b,這說明了當用戶訪問/b時url會保持/b但是路由匹配則為/a,就像用戶訪問/a一樣的。
const router = new VueRouter({ routes: [ { path: '/a', component: A, alias: '/b' } ] })
別名的功能可以將ui結構映射到任意的url,不受限於配置嵌套路由結構。需要注意的是別名不要用在path為'/'中,不然是不起作用的。
三、嵌套路由
路由組件配置中需求增加children字段,字段值結構一致,表示子路由路徑。
如果想在Second下添加兩個子路由,分別命名為childOne.vue和childTwo.vue。利用vue-router步驟如下:
1、在路由組件配置中增加children字段
const routes = [ { path: '/second', name: 'Second', component: () => import( '../components/Second.vue'), //嵌套路由
children:[ { path:'/Second/childOne', component:() => import( '../components/childOne.vue'), }, { path:'/Second/childTwo', component:() => import( '../components/childTwo.vue'), } ] }, { path:'*', redirect:'/' } ]
2、實現子路由路徑跳轉切換,就要新增一個<router-view>作為新的路由祝口,渲染在children所在的組件里,用<keep-alive>讓組件不銷毀。
<template>
<div>
<h1>{{msg}}</h1>
<router-link to="/second/childOne">childOne</router-link> ||
<router-link to="/second/childTwo">childTwo</router-link>
<keep-alive include='childTwo'><router-view></router-view></keep-alive>
</div>
</template>
四、導航鈎子
vue-router提供的導航鈎子主要是用來攔截導航,讓它完成跳轉或者取消。
有以下三種方式可以植入導航過程
1、全局
全局導航鈎子項目中運用較多的是:前置守衛,后置鈎子。可以使用router.beforeEach注冊一個全局的before鈎子
const router = new VueRouter({ ...}) router.beforeEach((to, from, next) => { //....
})
當一個導航觸發時,全局的before鈎子按照創建順序調用 ,鈎子是異步解析執行,此時導航在所鈎子reslove完一直是一個等待中的狀態。
每一個鈎子函數接收三個參數,參數的含義如下:
to: 即將要進入的目標路由對象,通俗來講就是要跳轉到的那個路徑
from:當前導航正要離開的路由,即從哪個路徑來的。
next:Function:一定要調用這個方法來reslove這個鈎子,執行效果依賴next方法調用的參數
示例:只是跳轉就修改頁面的title
要完成這個操作還需求用到路由元信息,那什么又是路由元信息呢?即定義路由的時候可配置meta字段,然后進行訪問。
//index.js
const routes = [ { path: '/', name: 'First', component: First, meta:{ //可以用來存儲一些自定義的信息
til:'首頁' }, } ]
//main.js
outer.beforeEach((to, from, next) => { document.title = to.meta.til || "example"; //console.log(to, from)
next(); })
next()函數傳遞不同參數有不同的情況,情況如下:
next():進行管理中的下一個鈎子,如果全部執行完成那導航的就是確認狀態;
next(false):中斷當前的導航。若瀏覽器url改變,那url的地址會重置到from路由對應的地址
next('/')&next({path:'/'}):跳轉到一個不同的地址,當前的導航被中斷,然后進行一個新的導航
需要注意的一點是:要確保要調用next方法,不然鈎子就不會被確認。next函數在任何給定的導航守衛中都被嚴格調用一次。它可以出現多於一次,但是只有在所有的邏輯路徑都不重疊的情況下,不然鈎子不會被解析或者是報錯。
示例:用戶登錄(假設已有login登錄頁面)
router.beforeEach((to, from, next) => { //需求注意的是:基本身就是要去login路徑,這時就不能再next('./login'),所以外面還要再進行一次判斷
if (to.path !== './login') { if (false) { next() } else { next('./login') } } else { next();//不傳參正常向下走
} })
beforeEach有攔截的功能,可以做全局的校驗,示例如下:
//使用鈎子函數對路由進行權限跳轉
router.beforeEach((to, from, next) => { const role = localStorage.getItem('ms_username'); if (!role && to.path !== './login') { next('./next'); } else if (to.mata.permission) { //如果是管理員權限則可進,模擬如下
role == 'admin' ? next() : next('/403'); } else { next(); } })
同樣的,我們也可以注冊一個全局的after鈎子,但它不像before鈎子那樣,after鈎子,它沒有next方法,不能改變導航。
router.afterEach(route => { //......
})
2、組件內部的導航鈎子
可以在路由組件內直接定義下面的導航鈎子
beforeRouteEnter
beforeRouterUpdate
beforeRouteLeave
const Foo = { template: `...`, beforeRouteEnter(to, from, next) { // 在渲染該組件的對應路由被 confirm 前調用 // 不!能!獲取組件實例 `this` // 因為當鈎子執行前,組件實例還沒被創建
}, beforeRouteUpdate(to, from, next) { // 在當前路由改變,但是該組件被復用時調用 // 舉例來說,對於一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候, // 由於會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鈎子就會在這個情況下被調用。 // 可以訪問組件實例 `this`
}, beforeRouteLeave(to, from, next) { // 導航離開該組件的對應路由時調用 // 可以訪問組件實例 `this`
} }
beforeRouteEnter鈎子不能訪問this,因為鈎子在導航確認前被調用,因此即將登場的新組件還沒被創建
不過,可以通過傳一個回調給 next來訪問組件實例。在導航被確認的時候執行回調,並且把組件實例作為回調方法的參數
beforeRouteEnter (to, from, next) { next(vm => { // 通過 `vm` 訪問組件實例
}) }
在 beforeRouteLeave 中直接訪問 this。這個 leave 鈎子通常用來禁止用戶在還未保存修改前突然離開。可以通過 next(false) 來取消導航
beforeRouteLeave的應用。有First,Second,childOne 三個頁面,Second頁面調到First頁面,需要First頁面進行緩存,childOne頁面調到First頁面,First頁面不需要緩存
要通過beforeRouteLeave這個鈎子來對路由里面的keepAlive進行賦值。從而動態確定First頁面是否需要被緩存。
First的路由 { path: '/', name: 'First', component: First, meta:{ keepAlive:true//需要被緩存 } }
--------------------------------------------------------------------- second頁面 export default { name: "Second", data() { return {} }, beforeRouteLeave(to,from,next){ to.meta.keepAlive= true;// second跳轉到first時,緩存,即不刷新 next(); }, };
--------------------------------------------------------------------- childOne頁面 name: "childOne", data() { return {} }, beforeRouteLeave(to,from,next){ to.meta.keepAlive= false;// childone跳到first時,first緩存,即刷新 next(); }, };
3、路由中配置的單個導航鈎子
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ...
}, beforeEnter: (route) => { // ...
} } ] });
五、數據獲取
有時候進入到某個路由后,需要從服務器上獲取數據,如渲染用戶信息時,需要從服務器獲取用戶的數據,這時可以通過兩種方式來完成。
導航完成后獲取:先完成導航,然后在接下來的組件生命周期獲取數據,在數據獲取期間顯示正在加載中之類的指示。使用這種方式會馬上導航和渲染維護,然后在組件的created鈎子中進行獲取數據,有機會在數據獲取的期間展示一個loading的狀態,還可以在不同視圖間展示不同的loading狀態。
假設有一個 Post 組件,需要基於 $route.params.id 獲取文章數據:
<template>
<div class="post">
<div v-if="loading" class="loading"> Loading... </div>
<div v-if="error" class="error"> {{ error }} </div>
<div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
----------------------------------------------------- export default { data () { return { loading: false, post: null, error: null } }, created () { // 組件創建完后獲取數據, // 此時 data 已經被 observed 了
this.fetchData() }, watch: { // 如果路由有變化,會再次執行該方法
'$route': 'fetchData' }, methods: { fetchData () { this.error = this.post = null
this.loading = true
getPost(this.$route.params.id, (err, post) => { this.loading = false
if (err) { this.error = err.toString() } else { this.post = post } }) } } }
導航完成之前就獲取:在導航完成前,在路由的enter鈎子中獲取數據,在數據獲取成功后執行導航。這種方式在導航轉入新的路由前就獲取了數據,可以在組件中的beforeRouteEnter鈎子中獲取數據,當數據獲取成功后調用next方法。
export default { data () { return { post: null, error: null } }, beforeRouteEnter (to, from, next) { getPost(to.params.id, (err, post) => { next(vm => vm.setData(err, post)) }) }, // 路由改變前,組件就已經渲染完了 // 邏輯稍稍不同
beforeRouteUpdate (to, from, next) { this.post = null getPost(to.params.id, (err, post) => { this.setData(err, post) next() }) }, methods: { setData (err, post) { if (err) { this.error = err.toString() } else { this.post = post } } } }
在為后面的視圖獲取數據時,用戶會停留在當前的界面,因此建議在數據獲取期間,顯示一些進度條或者別的指示。如果數據獲取失敗,同樣有必要展示一些全局的錯誤提醒。
六、router中的傳參
vue-router傳參主要分為兩個大類一是編程式的導航,二是聲明式的導航。
1、編程式導航(router.push):它傳遞的參數主要是分為兩類,一類是字符串,一類是對象。
為字符串的方式是直接將路由地址用字符串的方式來進行跳轉,這種方式簡單,但是不能傳遞參數,如下:
//this.$route這里存儲的都是屬性
//this.$router這里存儲的都是方法
// this.$router.push(參數) 參數等同於to后跟的內容
this.$router.push('/');// /為要跳轉的地址
為對象時它主要用到params和query來進行傳遞,使用query它是拼接到url的后面。可以用name也可以用path進行引入。當First.vue中有一個按鈕,點擊按鈕可以跳轉到Second.vue,進行參數傳遞,代碼如下:
First.vue //使用path引入
methods: { gosecond: function() { this.$router.push( { path: "second", query: { id:'123456',name:'tz' } }); } } //使用name引入
methods: { gosecond: function() { this.$router.push( { name: "second", query: { id:'123456',name:'tz' } }); } }, ---------------------------------------------- Second.vue mounted:function(){ this.id=this.$route.params.id; this.name = this.$route.params.name }
使用params有兩點需求注意,一是用name來進行引入,所以要確保路由中有name。二是要配置路由中的path,因為path它是路由的一部分,必須在路由后面添加上參數名。
First.vue <button @click="gosecond">按鈕</button> methods: { gosecond: function() { this.$router.push( { name: "Second", params: { id:'123456',name:'tz' } }); } } Second.vue mounted:function(){ this.id=this.$route.params.id; this.name = this.$route.params.name } index.js { path: '/second/:id/:name', name: 'Second', component: () => import( '../components/Second.vue') }
總結編程式導航三種方式:
this.$router.push("destination"),簡單但不能傳參;
this.$router.push({path(name):"destination",query:{id:xxx,name:xxx}})。
this.$router.push({name:"destination",params:{id:xxx,name:xxx}})。這種方式的不足是如果沒有設置路由中的path那在目標頁面刷新傳遞過來的參數不顯示會丟失。
2、聲明式導航<router-link>
聲明式的導航和編程式是一樣的。
字符串:
<router-link :to="{path:'/'}">First</router-link>
對象,使用query
<router-link :to="{path:'/',query:{id:'123',name:'first'}}">First</router-link>
對象,使用params
<router-link :to="{path:'/',params:{id:'123',name:'first'}}">First</router-link>