第4講 詳解如何在項(xiàng)目里配置路由(2)

上篇文章,我講了路由里面的,動(dòng)態(tài)路由匹配,編程式導(dǎo)航,嵌套路由匹配,命名路由,命名視圖,重定向這幾個(gè)知識(shí)點(diǎn),但是官網(wǎng)vue-router上,不僅僅是這些內(nèi)容,我提到的這些知識(shí)點(diǎn)是針對(duì)于真實(shí)的項(xiàng)目開發(fā)中用到進(jìn)行講解,如果想了解更多vue-router的知識(shí)可以查閱官網(wǎng)內(nèi)容

回憶一下上節(jié)的內(nèi)容,我們?cè)谥v動(dòng)態(tài)路由匹配和編程式導(dǎo)航的時(shí)候,組件中是如何獲取由路由傳遞過來的值呢?

動(dòng)態(tài)路由匹配:

<template>
    <section>
        {{$route.params.taskId}}
    </section>
</template>

編程式導(dǎo)航:

<template>
    <section>
        {{$route.query.taskId}}
    </section>
</template>

有沒有發(fā)現(xiàn),在具體組件里,我們使用的是$route.params$route.query來獲取路由傳遞過來的參數(shù),假如這個(gè)組件用到其它路由下面呢,當(dāng)路由不不攜帶參數(shù)時(shí),組件里有這么個(gè)玩意兒$route.query.taskId就會(huì)報(bào)錯(cuò),所以,采用上述兩種方式,當(dāng)需要組件復(fù)用時(shí),由于路由和組件之間有著高度的耦合,不能最大程度復(fù)用組件,那么就需要采用其它方式了,那就是路由組件傳參

  1. 路由組件傳參

路由組件傳參也有3中方式實(shí)現(xiàn):

第1種:布爾模式

修改src/router/index.js里面的路由:

  {
    path: '/task-detail/:taskId',
    name: 'task-detail',
    component: () => import('../views/task-detail.vue'),
    props: true
  },

組件里面獲取路由傳遞過來的值:

<template>
    <section>
        {{taskId}}
    </section>
</template>
<script>
export default {
    props: {
        taskId: {
            type: [String, Number],
            default: ''
        }
    },
    data () {
        return {
            
        }
    }
}
</script>

這樣,當(dāng)需要這個(gè)組件復(fù)用時(shí),要想給組件傳遞taskId這個(gè)值,只需要調(diào)用這個(gè)組件即可

<task-detail :taskId="10000218"></task-detail>

通過路由給組件傳值的時(shí)候要這樣寫:

<router-link to="/task-detail/10000217">任務(wù)詳情10000217</router-link>

第2種:對(duì)象模式

只需要修改src/router/index.js里面的路由,組件里面和布爾模式一樣:

  {
    path: '/task-detail',
    name: 'task-detail',
    component: () => import('../views/task-detail.vue'),
    props: {
      taskId: '10000218'
    }
  },

通過路由給組件傳值的時(shí)候要這樣寫:

<router-link to="/task-detail">任務(wù)詳情10000217</router-link>

第3種:函數(shù)模式

只需要修改src/router/index.js里面的路由,組件里面獲取值和上面兩種一樣:

  {
    path: '/task-detail/:taskId',
    name: 'task-detail',
    component: () => import('../views/task-detail.vue'),
    props: route => {
      if (route.params && route.params.taskId) {
        return {
          taskId: route.params.taskId
        }
      }
    }
  },

通過路由給組件傳值的時(shí)候要這樣寫:

<router-link to="/task-detail/10000217">任務(wù)詳情10000217</router-link>
  1. HTML5 History模式

講這個(gè)之前,我先來介紹一個(gè)html中錨點(diǎn)的概念,就拿我的寫著這個(gè)博客來說,一篇文章很長,超過你電腦一屏或者兩屏的內(nèi)容表,例如下面的文章內(nèi)容:

標(biāo)題

1. 小標(biāo)題1

2. 小標(biāo)題2

3. 小標(biāo)題3

...

我想快速訪問某個(gè)小標(biāo)題的內(nèi)容,可以這樣來寫

<a href="#title1">訪問小標(biāo)題1</a>
<a href="#title2">訪問小標(biāo)題1</a>
<a href="#title3">訪問小標(biāo)題1</a>

...

<a name="title1">1. 小標(biāo)題1</a>
<a name="title2">2. 小標(biāo)題2</a>
<a name="title3">3. 小標(biāo)題3</a>
<!--或則-->
<div id="title1">1. 小標(biāo)題1</a>
<div id="title2">2. 小標(biāo)題2</a>
<div id="title3">3. 小標(biāo)題3</a>

這就是錨點(diǎn)的內(nèi)容,這又和本節(jié)內(nèi)容有什么關(guān)系呢?vue-router官方說,vue-router默認(rèn)hash模式,hash是什么呢?我來介紹一下:

hash屬性是一個(gè)可讀可寫的字符串,該字符串是URL的錨部分(從#好開始的部分),#代表網(wǎng)頁中的一個(gè)位置,其右面的字符就是該位置的標(biāo)識(shí)符(說的就是錨點(diǎn)),例如:

http://www.blog.com/index.html#title1

就代表網(wǎng)頁index.html的title1位置,瀏覽器讀取這個(gè)URL后會(huì)自動(dòng)將title1位置滾動(dòng)至可視區(qū)域

是用來指導(dǎo)瀏覽器動(dòng)作的,對(duì)服務(wù)器端完全無用,所以,HTTP請(qǐng)求中不包括#,比如訪問下面的網(wǎng)址:

http://www.blog.com/index.html#title1

瀏覽器實(shí)際發(fā)出的請(qǐng)求是這樣的:

GET /index.html HTTP/1.1
Host: www.example.com

可以看到只請(qǐng)求了index.html,根本沒有#title1這部分

所以,在URL中,第一個(gè)#后面出現(xiàn)的任何字符,都會(huì)被瀏覽器解讀為位置標(biāo)識(shí)符(錨點(diǎn)),這些字符都不會(huì)被發(fā)送到服務(wù)器端,而且改變URL中#后面的部分,只相當(dāng)于改變了錨點(diǎn),瀏覽器只會(huì)滾動(dòng)到相應(yīng)位置,不會(huì)重新加載網(wǎng)頁,比如:

http://www.blog.com/index.html#title1
到
http://www.blog.com/index.html#title2

這種錨點(diǎn)的改變,完全由瀏覽器控制,而不會(huì)重新向服務(wù)器請(qǐng)求index.html這個(gè)頁面

現(xiàn)在我們?cè)倩氐絭ue-router官方文檔這里,它提到了,vue-router默認(rèn)hash模式(#后面跟字符串)使用hash來模擬一個(gè)完整的URL,于是當(dāng)URL改變時(shí),頁面不會(huì)重新加載,如果不想要這種方式展示,還可以用路由的history模式

const router = new VueRouter({
  mode: 'history',
  routes
})

這樣,路由就變化了:

http://localhost:4000/#/task-detail/10000216
變成了
http://localhost:4000/task-detail/10000217

但是這種模式也需要后端的支持,因?yàn)槲覀兊膽?yīng)用是個(gè)單頁客戶端應(yīng)用,只有一個(gè)index.html頁面,當(dāng)如有變化時(shí),如果采用hash模式,從路由:

http://localhost:4000/#/task-detail/10000217
變到
http://localhost:4000/#/about

時(shí),不會(huì)重新請(qǐng)求頁面,至始至終只有一個(gè)index.html頁面,路由的變化,也可以看成是錨點(diǎn)的改變,顯相當(dāng)于瀏覽器從#/task-detail這個(gè)錨點(diǎn)到/about這個(gè)錨點(diǎn),但是如果采用history模式,從路由:

http://localhost:4000/task-detail/10000217
變成了
http://localhost:4000/about

這個(gè)時(shí)候?yàn)g覽器就會(huì)認(rèn)為是需要向服務(wù)器請(qǐng)求task-detail.html和about.html這兩個(gè)html的,但是服務(wù)器上根本沒有這兩個(gè)html,就會(huì)報(bào)404文件未找到錯(cuò)誤,所以這個(gè)時(shí)候就需要后端哥們的支持,未匹配到html頁面的時(shí)候,就返回index.html這個(gè)頁面,具體后端怎么配置,可以參考官方文檔

  1. 導(dǎo)航守衛(wèi)

這部分是vue-router部分,在實(shí)際項(xiàng)目開發(fā)中也很有用處,用來判斷用戶是否登錄,或則有權(quán)限訪問某一個(gè)頁面,它幫助我們?cè)诼酚砂l(fā)生跳轉(zhuǎn)到導(dǎo)航結(jié)束這個(gè)過程中做一些邏輯處理,我分幾個(gè)部分來講

第一種:全局前置守衛(wèi)

來看下src/router/index.js里的router,這是vue-router的實(shí)例,它上面有一個(gè)beforeEach方法,功能就是進(jìn)行全局前置守衛(wèi)

咱們來看下如何使用:

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import errorRoutes from './error-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/task-detail/:taskId',
    name: 'task-detail',
    component: () => import('../views/task-detail.vue'),
    props: route => {
      if (route.params && route.params.taskId) {
        return {
          taskId: route.params.taskId
        }
      }
    }
  },
  {
    path: '/product',
    name: 'product',
    component: () => import('../views/product/index.vue'),
    children: [
      {
        path: 'ele-product', // 子路由需要前面加'/',只有副路由才有
        name: 'ele-product',
        component: () => import('../views/product/ele-product.vue'),
        children: [
          {
            path: 'phone', // 子路由需要前面加'/',只有副路由才有
            name: 'phone',
            components: {
              default: () => import('../views/product/phone.vue'),
              apple: () => import('../views/product/apple.vue'),
              mi: () => import('../views/product/mi.vue'),
              vivo: () => import('../views/product/vivo.vue'),
            },
          },
          {
            path: 'computer', // 子路由需要前面加'/',只有副路由才有
            name: 'computer',
            component: () => import('../views/product/computer.vue'),
          }
        ]
      }
    ]
  },
  ...errorRoutes,
  {
    path: '*',
    redirect: '/notFound'
  }
]

const router = new VueRouter({
  mode: 'history',
  routes
})

const whitelist = ['login', 'error401', 'error500', 'notFound', 'compatible', 'notLogin', '404', 'abnormal']

let app;
router.beforeEach((to, from, next) => {
    // const isLogin = !!sessionStorage.getItem('accessToken');
    const isLogin = true

    if (isLogin) {
        if (to.name === 'login') {
            next({
                name: 'home'
            });
        } else {
            next()
        }
    } else {
        if (whitelist.indexOf(to.name) !== -1) {
            next()
        } else {
            next({
                name: 'login'
            })
        }
    }
});
// next()方法一定要加,不然不能跳轉(zhuǎn)

router.afterEach((to, from) => {
    app = document.querySelector('.app-content-inner')
    app && app.scrollTo(0, 0)
})

export default router

上面的代碼很好讀懂,我就不做一一介紹,大概邏輯就是從本地session拿token看是否已經(jīng)登錄,登錄了直接跳轉(zhuǎn)到首頁,沒有登錄,看看當(dāng)前路由是否在白名單那里,不在直接跳轉(zhuǎn)到登錄頁登錄

第二種:全局后置鉤子

router實(shí)例下的afterEach方法就是全局后置鉤子,和前置守衛(wèi)不同的是,這些鉤子不會(huì)接受next函數(shù),也不會(huì)改變導(dǎo)航本省

第三種:路由獨(dú)享的守衛(wèi)

  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
    beforeEnter: (to, from, next) => {
      if (from.name === 'Home') {
        console.log('從home頁跳轉(zhuǎn)過來')
      } else {
        console.log('不是從home頁跳轉(zhuǎn)來的')
      }
      next()
    }
  },

第四種: 組件內(nèi)守衛(wèi)

beforeRouteEnter

提到這個(gè)的時(shí)候,想起來前幾天做項(xiàng)目遇到一個(gè)問題,我有一個(gè)創(chuàng)建任務(wù)的頁面(http://localhost:8082/#/web-task/task-create),分別可以從兩個(gè)頁面:監(jiān)測任務(wù)列表(http://localhost:8082/#/web-task/web-list)和監(jiān)測任務(wù)管理(http://localhost:8082/#/web-task/task-list)跳轉(zhuǎn)過來,需求是,從哪個(gè)頁面跳轉(zhuǎn)過來的,當(dāng)任務(wù)創(chuàng)建完畢還回到哪個(gè)頁面

常規(guī)思路就是在創(chuàng)建任務(wù)的頁面監(jiān)聽路由的變化,拿到from.path里的值,也就是上個(gè)頁面的路由,但是怎么都監(jiān)聽不到路由的變化,這個(gè)時(shí)候我就想到了beforeRouteEnter使用組件內(nèi)守衛(wèi),可以拿到to和from

beforeRouteEnter (to, from, next) {
    console.log(to.name)
    console.log(from.name)
    console.log(this) // undefined
    next()
}

但是我發(fā)現(xiàn)在里面拿不到this這個(gè)vue實(shí)例,解釋原因是因?yàn)椋鹤哌@一步的時(shí)候,當(dāng)前組件還沒有渲染完成,vue實(shí)例還未創(chuàng)建完成,但是我非要使用腫么辦?

解決方法就是給next函數(shù)傳一個(gè)回調(diào)函數(shù),完美解決這個(gè)問題

beforeRouteEnter (to, from, next) {
  next(vm => {
    if (from.name === 'web_list') {
        vm.from_router = '/web-task/web-list'
    } else if (from.name === 'task_list') {
        vm.from_router = '/web-task/task-list'
    }
  })
}

beforeRouteLeave

關(guān)于這個(gè)的用法,比如用戶在當(dāng)前頁面進(jìn)行編輯操作,還沒有保存就要跳轉(zhuǎn)到其它頁面,那么你就可以在這個(gè)鉤子函數(shù)里面提醒用戶,編輯還未完成,是否取消編輯,這里提示一下:在這個(gè)方法里可以直接用this

<script>
export default {
    props: {
        taskId: {
            type: [String, Number],
            default: ''
        }
    },
    data () {
        return {
            
        }
    },
    methods: {
        
    },
    beforeRouteLeave (to, from, next) {
        const leave = confirm('確定離開嗎?')
        if (leave) {
            next()
        } else {
            next(false)
        }
        // next(vm => {
        //     console.log(vm) // vue實(shí)例
        // })
    },
    beforeRouteUpdate (to, from, next) {
        console.log('組件被復(fù)用')
        next()
    }
}
</script>

beforeRouteUpdate

// 在當(dāng)前路由改變,但是該組件被復(fù)用時(shí)調(diào)用
// 舉例來說,對(duì)于一個(gè)帶有動(dòng)態(tài)參數(shù)的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉(zhuǎn)的時(shí)候,
// 由于會(huì)渲染同樣的 Foo 組件,因此組件實(shí)例會(huì)被復(fù)用。而這個(gè)鉤子就會(huì)在這個(gè)情況下被調(diào)用。
// 可以訪問組件實(shí)例 `this`
beforeRouteUpdate (to, from, next) {
  console.log('組件被復(fù)用')
  next()
}

第五種 路由元信息

{
  path: '/',
  name: 'Home',
  component: Home,
  meta: {
    title: '首頁',
    requiresAuth: ['admin', 'user']
  }
},

這里的meta就是元信息,可以在這里給每個(gè)路由對(duì)象配一個(gè)title或者打一個(gè)標(biāo)志,用來區(qū)別哪些用戶可以訪問這個(gè)路由

接下來,我講一個(gè)例子,利用前置守衛(wèi)和路由元信息,修改window.document.title的值

首先找到咱們?cè)诘诙?jié)新建的src/lib/util.js,當(dāng)時(shí)說了這個(gè)文件用來存放和業(yè)務(wù)相關(guān)的方法,接下來咱們就新建一個(gè)方法

// util.js
export const setTitle = (title) => {
  window.document.title = title ? title + '-撥測管理平臺(tái)' : '撥測管理平臺(tái)'
}

然后在src/router/index.js里面引入,并且在前置守衛(wèi)里增加一行代碼

// router/index.js
import {setTitle} from '@/lib/util'

router.beforeEach((to, from, next) => {
  to.meta && setTitle(to.meta.title)
})

第六種 過渡效果

路由切換的時(shí)候,在<router-view>里面加載頁面,我們可以利用<transition>組件給它添加一些過渡效果

<transition>
  <router-view></router-view>
</transition>

如果是多個(gè)視圖,需要用<transition-group>包裹

<transition-group>
  <router-view></router-view>
  <router-view name="phone"></router-view>
</transition-group>

我來寫一個(gè)過渡效果的例子:

<transition name="router">
  <router-view/>
</transition>

<style lang="less">
// 進(jìn)入效果
.router-enter {
  opacity: 0;
}
.router-enter-active {
  transition: opacity 1s ease;
}
.router-enter-to {
  opacity: 1;
}
// 離開效果
.router-leave {
  opacity: 1;
}
.router-leave-active {
  transition: opacity 1s ease;
}
.router-leave-to {
  opacity: 0;
}
</style>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,654評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373