Vue開發之路由進階

renpingsheng發表於2019-05-26

1.路由元件傳參

在一個頁面中,需要根據路由獲得引數,然後在頁面進行邏輯處理,可以通過$route來獲取相關引數

但是這樣一來,頁面元件與路由耦合太高,為了解耦,頁面元件可以在更大程度上進行復用,可以使用路由組組傳參

路由元件傳參有三種形式

1.1 第一種:布林模式

適用於在動態路由匹配中,有動態路由引數的路由配置中

在前面的例子裡,定義了一個動態路由引數的路由

修改src/router/router/js檔案

import Home from '@/views/Home.vue'

export default [
    {
        path: '/',
        name: 'home',
        alias:'/home_page',
        component: Home,
    }, {
        path: '/about',
        name: 'about',
        component: () => import('@/views/About.vue'),
    },{
        path:'/argu/:name',
        name:'argu',
        component:() => import('@/views/argu.vue'),
        props:true
    }
]

修改src/views/argu.vue檔案內容為:

<template>
    <div>
        {{ $route.params.name }}
    </div>
</template>

<script>
export default {

}
</script>

用瀏覽器開啟URL:http://localhost:8080/#/argu/orange,效果如下

Vue開發之路由進階

在上面的argu頁面,可以通過$route物件獲取動態路由中的引數,此時也可以使用布林模式獲取動態路由中定義的引數

修改src/router/router/js檔案,定義布林模式來獲取動態路由引數

import Home from '@/views/Home.vue'

export default [
    {
        path: '/',
        name: 'home',
        alias:'/home_page',
        component: Home,
    }, {
        path: '/about',
        name: 'about',
        component: () => import('@/views/About.vue'),
    },{
        path:'/argu/:name',
        name:'argu',
        component:() => import('@/views/argu.vue'),
        props:true      // 定義布林模式獲取路由引數
    }
]

修改src/views/argu.vue檔案

<template>
    <div>
        {{ name }}
    </div>
</template>

<script>
export default {
    props:{
        name:{
            type:String,            // 定義name的值的型別,String表示name的值必須為字串,Number表示name的值為整數
            default:'renpingsheng'  // 定義預設值,可以省略
        }
    }
}
</script>

更改路由的引數,效果如下

Vue開發之路由進階

1.2 第二種:物件模式

普通路由的傳值,例如在about路由頁面中傳參

修改src/router/router.js檔案,在about路由中傳值

import Home from '@/views/Home.vue'

export default [
    {
        path: '/',
        name: 'home',
        alias:'/home_page',
        component: Home,
    }, {
        path: '/about',
        name: 'about',
        component: () => import('@/views/About.vue'),
        props:{
            fruit:"banana"
        }
    },{
        path:'/argu/:name',
        name:'argu',
        component:() => import('@/views/argu.vue'),
        props:true
    }
]

然後修改src/views/About.vue檔案

<template>
<div class="about">
    <h1>This is an about page</h1>
    <b>{{ fruit }}</b>
</div>
</template>
<script>
export default {
    props:{
        fruit:{
            type:String,
            default:'apple'     // 預設值,如果路由中沒的傳遞任何值則在頁面上顯示預設值
        }
    }
}
</script>

由於/about路由中傳遞了fruit的值,所以頁面顯示效果如下

Vue開發之路由進階

此時如果在路由的props屬性中沒有傳遞fruit的值

import Home from '@/views/Home.vue'

export default [
    {
        path: '/',
        name: 'home',
        alias:'/home_page',
        component: Home,
    }, {
        path: '/about',
        name: 'about',
        component: () => import('@/views/About.vue'),
        props:{
            // fruit:"banana"       // 把傳遞引數的這一行註釋
        }
    },{
        path:'/argu/:name',
        name:'argu',
        component:() => import('@/views/argu.vue'),
        props:true
    }
]

src/views/About.vue檔案內容不變,則頁面上就會顯示About.vue檔案中props屬性中定義的預設值,顯示效果如下

Vue開發之路由進階

1.3 第三種:函式模式

適合在傳入的屬性中能夠根據當前的路由進行邏輯判斷,從而設定傳入元件的屬性值

修改src/router/router/js檔案,修改home路由

import Home from '@/views/Home.vue'

export default [
    {
        path: '/',
        name: 'home',
        alias:'/home_page',
        component: Home,
        props: route => ({
            fruit: route.query.params
        })
    }, {
        path: '/about',
        name: 'about',
        component: () => import('@/views/About.vue'),
        props:{
            // fruit:"banana"
        }
    },{
        path:'/argu/:name',
        name:'argu',
        component:() => import('@/views/argu.vue'),
        props:true
    }
]

修改src/views/Home.vue檔案

<template>
<div class="home">
    <b>{{ fruit }}</b>
    <br>
    <button @click="handleClick">返回上一頁</button>
</div>
</template>

<script>

export default {
    name: 'home',
    props:{
        fruit:{
            type: String,
            default:'strawberry'
        }
    },
    methods:{
        handleClick () {
            this.$router.push({
            name:`argu`,
            params:{
                name:'banana'
            }
            })
        }
    }
}
</script>

此時用瀏覽器開啟URL: http://localhost:8080/#/?params=orange,顯示效果如下

Vue開發之路由進階

刪除URL中新增的params鍵值對,頁面顯示效果如下

Vue開發之路由進階

當URL中包含params鍵值對時,頁面上就會顯示URL中params的值

當URL中沒有傳入params鍵值對時,頁面上就會顯示props中定義的預設值

2.導航守衛

導航守衛在實際開發中都會用到的技能,大致相當於後端開發中的中介軟體功能

能夠在路由發生跳轉到導航結束這個階段內進行一些相應的邏輯處理

比如使用者開啟某個需要登入的頁面時判斷使用者是否登入,如果使用者已經登入則直接開啟頁面

如果使用者沒有登入就跳轉到登入頁面

再比如使用者在一個頁面編輯了內容,當使用者跳轉到別的頁面時提醒使用者是否儲存等功能都可以由導航守衛來完成

2.1 全域性守衛

全域性守衛就是在全域性設定的守衛

修改src/router/index.js檔案,新增全域性守衛

import Vue from 'vue'
import Router from 'vue-router'
import routes from './router'

Vue.use(Router)

const router = new Router({
    routes
})

const IS_LOGINED = true         // 模擬使用者是否登入

router.beforeEach((to,from,next) => {
    if(to.name !== 'login'){
        if(IS_LOGINED) next()
        else next({name:'login'})
    }else{
        if(IS_LOGINED) next({name:'home'})
        else next()
    }
})
export default router

修改src/router/router.js檔案,新增login命名路由

import Home from '@/views/Home.vue'

export default [
    {
        path: '/',
        name: 'home',
        alias:'/home_page',
        component: Home,
        props: route => ({
            fruit: route.query.params
        })
    }, {
        path: '/about',
        name: 'about',
        component: () => import('@/views/About.vue'),
        props:{
            // fruit:"banana"
        }
    },{
        path:'/argu/:name',
        name:'argu',
        component:() => import('@/views/argu.vue'),
        props:true
    },{
        path:'/login',
        name:'login',
        component:() => import('@/views/login.vue')
    }
]

然後在src/views/目錄下新建login.vue檔案,檔案內容如下

<template>
    <div>
        <b>this is login page</b>
    </div>
</template>

<script>
export default {
    
}
</script>

在瀏覽器中輸入路由列表中已配置的任何URL,都會進入對應的頁面

當把src/router/index.js檔案中IS_LOGINED的值設定為false

import Vue from 'vue'
import Router from 'vue-router'
import routes from './router'

Vue.use(Router)

const router = new Router({
    routes
})

const IS_LOGINED = false

router.beforeEach((to,from,next) => {
    if (to.name !== 'login') {
        if (IS_LOGINED) next()
        else next({ name: 'login' })
    } else {
        if (IS_LOGINED) next({ name: 'home' })
        else next()
    }
})
export default router

在瀏覽器中輸入任何URL,都會進入login頁面

Vue開發之路由進階

每個守衛方法接收三個引數:

to: Route: 即將要進入的目標 路由物件
from: Route: 當前導航正要離開的路由
next: Function: 一定要呼叫該方法來 resolve 這個鉤子。執行效果依賴 next 方法的呼叫引數。
其中,next方法可以傳入的引數:
    next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。
    next(false): 中斷當前的導航。如果瀏覽器的 URL 改變了 (可能是使用者手動或者瀏覽器後退按鈕),那麼 URL 地址會重置到 from 路由對應的地址。
    next('/') 或者 next({ path: '/' }): 跳轉到一個不同的地址。當前的導航被中斷,然後進行一個新的導航。你可以向 next 傳遞任意位置物件,且允許設定諸如 replace: true、name: 'home' 之類的選項以及任何用在 router-link 的 to prop 或 router.push 中的選項。

一定要確保在所有路由守衛最後要執行next方法,否則鉤子就不會被 resolved

2.2 後置守衛

後置守衛不能阻止頁面的跳轉

修改src/router/index.js檔案,設定後置守衛

import Vue from 'vue'
import Router from 'vue-router'
import routes from './router'

Vue.use(Router)

const router = new Router({
    routes
})

const IS_LOGINED = true

router.beforeEach((to,from,next) => {
    if (to.name !== 'login') {
        if (IS_LOGINED) next()
        else next({ name: 'login' })
    } else {
        if (IS_LOGINED) next({ name: 'home' })
        else next()
    }
})

router.afterEach((to, from) => {
    console.log(from)
    console.log(to)
})
export default router

先用瀏覽器開啟home頁面,再跳轉到about頁面,根據後置守衛中列印出的結果

Vue開發之路由進階

2.3 beforeResolve前端守衛

beforeResolve也是全域性守衛,beforeResolve作用於導航被確認之前在所有元件內守衛以及非同步路由被解析之後守衛被呼叫

導航被確認的表示所有導航鉤子都結束,則表示導航被確認

2.4 路由獨享守衛

路由獨享守衛是在路由列表中配置的,例如在home路由中配置一個只在home路由中被呼叫的守衛

修改src/router/router.js檔案,為home路由新增路由獨享守衛

import Home from '@/views/Home.vue'

export default [
    {
        path: '/',
        name: 'home',
        alias:'/home_page',
        component: Home,
        props: route => ({
            fruit: route.query.params
        }),
        beforeEnter:(to,from,next) => {
            if(from.name === 'about') alert("這是從about頁來的")
            else alert("這不是從about頁來的")
            next()
        }
    }, {
        path: '/about',
        name: 'about',
        component: () => import('@/views/About.vue'),
        props:{
            // fruit:"banana"
        }
    },{
        path:'/argu/:name',
        name:'argu',
        component:() => import('@/views/argu.vue'),
        props:true
    },{
        path:'/login',
        name:'login',
        component:() => import('@/views/login.vue')
    }
]

此時,用瀏覽器開啟home頁,然後跳轉到about頁,再從about頁跳轉回到home頁,則會彈出對話方塊

Vue開發之路由進階

取消彈出框,在瀏覽器中輸入URL: http://localhost:8080/#/argu/orange,再從argu頁面進入home頁面,則會彈出對話方塊

Vue開發之路由進階

如果新增了beforeEnter路由獨享守衛,在進行邏輯處理後,一定要呼叫 next()方法,否則不會進行跳轉

2.5 元件內守衛

每一個元件都可以有三個鉤子

註釋其他的守衛,修改src/views/Home.vue檔案,新增元件內守衛

<template>
<div class="home">
    <b>{{ fruit }}</b>
    <br>
    <button @click="handleClick">返回上一頁</button>
</div>
</template>

<script>

export default {
    name: 'home',
    props:{
        fruit:{
            type: String,
            default:'strawberry'
        }
    },
    beforeRouteEnter(to,from,next){
        console.log(this)
        console.log(from.name)
        console.log(to.name)
        next()
    },
    methods:{
        handleClick () {
            this.$router.push({
            name:`argu`,
            params:{
                name:'banana'
            }
            })
        }
    }
}
</script>

用瀏覽器開啟URL: http://localhost:8080/#/,然後跳轉到About頁面,再跳轉回Home頁

Vue開發之路由進階

在瀏覽器的除錯頁面可以看到

Vue開發之路由進階

當直接開啟Home頁面時,from.name的值是null,to.name的值為home
而當從About頁面跳轉到Home頁面時,from.name的值是about,to.name的值仍然是home

元件內守衛是在路由已經被觸發,在進入對應頁面呼叫,此時對應頁面還沒有渲染,所以在元件內守衛中呼叫this時,是獲取不到當前元件的例項的,正如上面的例子裡其值為undefined

跟路由獨享守衛一樣,如果新增了beforeRouteEnter守衛,在進行邏輯處理後,一定要呼叫 next()方法,否則不會進行跳轉
如果想在元件內守衛中獲取當前元件的例項,可以在next方法中獲取

修改src/views/Home.vue檔案,在元件內守衛的next方法中獲取元件例項

<template>
<div class="home">
    <b>{{ fruit }}</b>
    <br>
    <button @click="handleClick">返回上一頁</button>
</div>
</template>

<script>

export default {
    name: 'home',
    props:{
        fruit:{
            type: String,
            default:'strawberry'
        }
    },
    beforeRouteEnter(to,from,next){
        next( vm => {
            console.log(vm)
            console.log(vm.$root)
        })
    },
    methods:{
        handleClick () {
            this.$router.push({
            name:`argu`,
            params:{
                name:'banana'
            }
            })
        }
    }
}
</script>

重新整理瀏覽器,列印結果如下

Vue開發之路由進階

可以通過回撥函式的方式來獲取當前元件的例項,然後就可以獲取某個屬性的結果了

修改src/views/Home.vue檔案,新增beforeRouteLeave守衛

<template>
<div class="home">
    <b>{{ fruit }}</b>
    <br>
    <button @click="handleClick">返回上一頁</button>
</div>
</template>

<script>

export default {
    name: 'home',
    props:{
        fruit:{
            type: String,
            default:'strawberry'
        }
    },
    beforeRouteEnter(to,from,next){
        next( vm => {
            console.log(vm)
            console.log(vm.$root)
        })
    },
    beforeRouteLeave(to,from,next){
        const is_leave = confirm("您確定要離開本頁面嗎?")
        if(is_leave) next()
        else next(false)
    },
    methods:{
        handleClick () {
            this.$router.push({
            name:`argu`,
            params:{
                name:'banana'
            }
            })
        }
    }
}
</script>

重新整理瀏覽器,顯示效果如下

Vue開發之路由進階

點選About標籤後,顯示效果如下

Vue開發之路由進階

點選確定按鈕,頁面會跳轉到About頁面

如果點選取消按鈕,則頁面不會跳轉,仍然會停留在Home頁面

在這個守衛被呼叫時,頁面已經渲染好了,所以可以在beforeRouteLeave元件裡使用this來獲取元件例項

比如我們一個網頁發表一篇部落格,當誤操作點選跳轉到其他頁面時,此時就可以使用這個守衛來提示使用者是否儲存當前的編輯內容

2.6 beforeRouteUpdate守衛

修改src/views/argu.vue檔案,新增beforeRouteUpdate守衛

<template>
    <div>
        {{ name }}
    </div>
</template>

<script>
export default {
    props:{
        name:{
            type:String,
        }
    },
    beforeRouteUpdate(to,from,next){
        console.log(to.name, from.name)
    }
}
</script>

使用瀏覽器開啟URL:http://localhost:8080/#/argu/apple,此時在瀏覽器的除錯頁面不會列印任何資料

Vue開發之路由進階

修改URL為:http://localhost:8080/#/argu/orange,頁面顯示效果如下

Vue開發之路由進階

第一次進入argu頁面時,沒有列印任何內容,說明beforeRouteUpdate守衛沒有被呼叫
當路由不改變,路由引數改變時,argu元件被複用,beforeRouteUpdate守衛才被呼叫

與beforeRouteLeave守衛一樣,在beforeRouteUpdate守衛裡,頁面已經渲染好了,所以可以使用this來獲取元件例項

3.一個完整的導航解析流程

導航被觸發,不管是使用this.$router.push方式觸發,還是在瀏覽器中直接輸入URL的方式觸發,
在失活的元件(即將離開的頁面元件)裡呼叫離開守衛 beforeRouteLeave
呼叫全域性的前置守衛 beforeEach
在重用的元件裡呼叫 beforeRouteUpdate 
呼叫路由獨享守衛 beforeEnter
解析非同步路由元件
在被啟用的元件(即將進入的頁面元件)裡呼叫beforeRouteEnter
呼叫全域性的解析守衛 beforeResolve
導航被確認
呼叫全域性的後置守衛 afterEash
觸發DOM更新
用建立好的例項呼叫 beforeRouterEnter守衛裡傳給next的回撥函式

4.路由切換的動態效果

路由的切換其實就是一個路由登出,另一個路由載入

修改src/App.vue檔案,

<template>
<div id="app">
    <div id="nav">
    <router-link v-bind:to="{ name:'home'}">Home</router-link> |
    <router-link :to="{ name:'about'}">About</router-link>
    </div>
    <transition-group name="router">
    <router-view key="defalut"/>
    <router-view key="view1" name="view1"/>
    <router-view key="view2" name="view2"/>
    </transition-group>
</div>
</template>

<style lang="less">
.router-enter{
    opacity: 0;
}
.router-enter-active{
    transition: opacity 2s ease;
}
.router-enter-to {
    opacity:1;
}
.router-leave{
    opacity:1;
}
.router-leave-active{
    transition:opacity 2s ease;
}
.router-leave-to {
    opacity:0;
}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
}

#nav a {
font-weight: bold;
color: #2c3e50;
}

#nav a.router-link-exact-active {
color: #42b983;
}
</style>

重新整理瀏覽器,切換路由時,可以看到路由切換時,頁面都有動態效果

相關文章