vue-router進階

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

導(dǎo)航表示路由正在發(fā)生改變。vue-router提供的導(dǎo)航守衛(wèi)主要用來通過跳轉(zhuǎn)或取消的方式守衛(wèi)導(dǎo)航。有多種機會植入路由導(dǎo)航過程中:全局的,單個路由獨享的,或者組件級的。
參數(shù)或查詢的改變并不會觸發(fā)進入/離開的導(dǎo)航守衛(wèi)。你可以通過觀察$route對象來應(yīng)對這些變化,或使用beforeRouteUpdate的組件內(nèi)守衛(wèi)。

全局前置守衛(wèi)

可以使用router.beforeEach注冊一個全局前置守衛(wèi)。

const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
  // ...
})

當一個導(dǎo)航觸發(fā)時,全局前置守衛(wèi)按照創(chuàng)建順序調(diào)用。守衛(wèi)是異步解析執(zhí)行,此時導(dǎo)航在所有守衛(wèi)resolve完之前一直處于等待中。
每個守衛(wèi)方法接收三個參數(shù):

  • to:Route:即將要進入的目標路由對象
  • from:Route:當前導(dǎo)航正要離開的路由
  • next:Function:一定要調(diào)用該方法來resolve這個鉤子。執(zhí)行效果依賴next方法的調(diào)用參數(shù)。
    • next(): 進行管道中的下一個鉤子。如果全部鉤子執(zhí)行完了,則導(dǎo)航的狀態(tài)就是confirmed(確認的)。
    • next(false):中斷當前的導(dǎo)航。如果瀏覽器的URL改變了(可能是用戶手動或者瀏覽器后退按鈕),那么URL地址會重置到from路由對應(yīng)的地址。
    • next('/')或者next({path:'/'}):跳轉(zhuǎn)到一個不同的地址。當前的導(dǎo)航被中斷,然后進行一個新的導(dǎo)航。你可以向next傳遞任意位置對象,且允許設(shè)置諸如replace:true、name:'home'之類的選項以及任何用在router-linkto proprouter.push中的選項。
    • next(error):如果傳入next的參數(shù)是一個Error實例,則導(dǎo)航會被終止且該錯誤會被傳遞給router.onError()注冊過的回調(diào)。

確保要調(diào)用next方法,否則鉤子就不會被resolved。

全局解析守衛(wèi)

在2.5.0+可以用router.beforeResolve注冊一個全局守衛(wèi)。這和router.beforeEach類似,區(qū)別是在導(dǎo)航被確認之前,同時在所有組件內(nèi)守衛(wèi)和異步路由組件被解析之后,解析守衛(wèi)就被調(diào)用。

全局后置鉤子

也可以注冊全局后置鉤子,然而和守衛(wèi)不同的是,這些鉤子不會接受next函數(shù)也不會改變導(dǎo)航本身。

router.afterEach((to, from) => {
  // ...
})

路由獨享的守衛(wèi)

可以在路由配置上直接定義beforeEnter守衛(wèi)。

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

這些守衛(wèi)與全局前置守衛(wèi)的方法參數(shù)是一樣的。

組件內(nèi)的守衛(wèi)

可以在路由組件內(nèi)直接定義以下路由導(dǎo)航守衛(wèi)。

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染該組件的對應(yīng)路由被 confirm 前調(diào)用
    // 不!能!獲取組件實例 `this`
    // 因為當守衛(wèi)執(zhí)行前,組件實例還沒被創(chuàng)建
  },
  beforeRouteUpdate (to, from, next) {
    // 在當前路由改變,但是該組件被復(fù)用時調(diào)用
    // 舉例來說,對于一個帶有動態(tài)參數(shù)的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉(zhuǎn)的時候,
    // 由于會渲染同樣的 Foo 組件,因此組件實例會被復(fù)用。而這個鉤子就會在這個情況下被調(diào)用。
    // 可以訪問組件實例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 導(dǎo)航離開該組件的對應(yīng)路由時調(diào)用
    // 可以訪問組件實例 `this`
  }
}

beforeRouteEnter守衛(wèi)不能訪問this,因為守衛(wèi)在導(dǎo)航確認前被調(diào)用,因此即將登場的新組件還沒被創(chuàng)建。
不過,可以通過傳一個回調(diào)給next來訪問組件實例。在導(dǎo)航被確認的時候執(zhí)行回調(diào),并且把組件實例作為回調(diào)方法的參數(shù)。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通過 `vm` 訪問組件實例
  })
}

注意beforeRouteEnter是支持給next傳遞回調(diào)的唯一守衛(wèi)。對于beforeRouteUpdatebeforeRouteLeave來說,this已經(jīng)可用了,所以不支持傳遞回調(diào),因為沒有必要了。

beforeRouteUpdate (to, from, next) {
  // just use `this`
  this.name = to.params.name
  next()
}

這個離開守衛(wèi)通常用來禁止用戶在還未保存修改前突然離開。該導(dǎo)航可以通過next(false)來取消。

beforeRouteLeave (to, from , next) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
    next()
  } else {
    next(false)
  }
}

完整的導(dǎo)航解析流程

  1. 導(dǎo)航被觸發(fā)。
  2. 在失活的組件里調(diào)用離開守衛(wèi)。
  3. 調(diào)用全局的beforeEach守衛(wèi)。
  4. 在重用的組件里調(diào)用beforeRouteUpdate守衛(wèi)。
  5. 在路由配置里調(diào)用beforeEnter。
  6. 解析異步路由組件。
  7. 在被激活的組件里調(diào)用 beforeRouteEnter。
  8. 調(diào)用全局的beforeResolve守衛(wèi) (2.5+)。
  9. 導(dǎo)航被確認。
  10. 調(diào)用全局的afterEach鉤子。
  11. 觸發(fā)DOM更新。
  12. 用創(chuàng)建好的實例調(diào)用beforeRouteEnter守衛(wèi)中傳給next的回調(diào)函數(shù)。

路由元信息

定義路由的時候可以配置meta字段。

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})

我們稱呼routes配置中的每個路由對象為路由記錄。路由記錄可以是嵌套的,因此,當一個路由匹配成功后,他可能匹配多個路由記錄。
例如,根據(jù)上面的路由配置,/foo/bar這個URL將會匹配父路由記錄以及子路由記錄。
一個路由匹配到的所有路由記錄會暴露為$route對象(還有在導(dǎo)航守衛(wèi)中的路由對象)的$route.matched數(shù)組。因此,我們需要遍歷$route.matched來檢查路由記錄中的meta字段。
下面例子展示在全局導(dǎo)航守衛(wèi)中檢查元字段。

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // 確保一定要調(diào)用 next()
  }
})

過渡動效

<router-view>是基本的動態(tài)組件,所以我們可以用<transition>組件給它添加一些過渡效果。

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

單個路由的過渡

上面的用法會給所有路由設(shè)置一樣的過渡效果,如果你想讓每個路由組件有各自的過渡效果,可以在各路由組件內(nèi)使用<transition>并設(shè)置不同的name

const Foo = {
  template: `
    <transition name="slide">
      <div class="foo">...</div>
    </transition>
  `
}
const Bar = {
  template: `
    <transition name="fade">
      <div class="bar">...</div>
    </transition>
  `
}

基于路由的動態(tài)過渡

還可以基于當前路由與目標路由的變化關(guān)系,動態(tài)設(shè)置過渡效果:

<!-- 使用動態(tài)的 transition name -->
<transition :name="transitionName">
  <router-view></router-view>
</transition>
// 接著在父組件內(nèi)
// watch $route 決定使用哪種過渡
watch: {
  '$route' (to, from) {
    const toDepth = to.path.split('/').length
    const fromDepth = from.path.split('/').length
    this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
  }
}

數(shù)據(jù)獲取

有時候,進入某個路由后,需要從服務(wù)器獲取數(shù)據(jù)。例如,在渲染用戶信息時,你需要從服務(wù)器獲取用戶的數(shù)據(jù)。我們可以通過兩種方式來實現(xiàn):

  • 導(dǎo)航完成之后獲?。合韧瓿蓪?dǎo)航,然后在接下來的組件生命周期鉤子中獲取數(shù)據(jù)。在數(shù)據(jù)獲取期間顯示加載中之類的指示。
  • 導(dǎo)航完成之前獲取:導(dǎo)航完成前,在路由進入的守衛(wèi)中獲取數(shù)據(jù),在數(shù)據(jù)獲取成功后執(zhí)行導(dǎo)航。

導(dǎo)航完成后獲取數(shù)據(jù)

使用這種方式時,會馬上導(dǎo)航和渲染組件,然后在組件的created鉤子中獲取數(shù)據(jù)。這讓我們有機會在數(shù)據(jù)獲取期間展示一個loading狀態(tài),還可以在不同視圖間展示不同的loading狀態(tài)。
假設(shè)我們有一個Post組件,需要基于$route.params.id獲取文章數(shù)據(jù)。

<template>
  <div class="post">
    <div class="loading" v-if="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>
<script>
export default {
  data () {
    return {
      loading: false,
      post: null,
      error: null
    }
  },
  created () {
    // 組件創(chuàng)建完后獲取數(shù)據(jù),
    // 此時 data 已經(jīng)被 observed 了
    this.fetchData()
  },
  watch: {
    // 如果路由有變化,會再次執(zhí)行該方法
    '$route': 'fetchData'
  },
  methods: {
    fetchData () {
      this.error = this.post = null
      this.loading = true
      // replace getPost with your data fetching util / API wrapper
      getPost(this.$route.params.id, (err, post) => {
        this.loading = false
        if (err) {
          this.error = err.toString()
        } else {
          this.post = post
        }
      })
    }
  }
}
</script>

在導(dǎo)航完成前獲取數(shù)據(jù)

通過這種方式,我們在導(dǎo)航轉(zhuǎn)入新的路由前獲取數(shù)據(jù)。我們可以在接下來的組件的beforeRouteEnter守衛(wèi)中獲取數(shù)據(jù),當數(shù)據(jù)獲取成功后只調(diào)用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))
    })
  },
  // 路由改變前,組件就已經(jīng)渲染完了
  // 邏輯稍稍不同
  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
      }
    }
  }
}

在為后面的視圖獲取數(shù)據(jù)時,用戶會停留在當前的界面,因此建議在數(shù)據(jù)獲取期間,顯示一些進度條或者別的指示。如果數(shù)據(jù)獲取失敗,同樣有必要展示一些全局的錯誤提醒。

滾動行為

使用前端路由,當切換到新路由時,想要頁面滾到頂部,或者是保持原先的滾動位置,就像重新加載頁面那樣。vue-router能做到,而且更好,它讓你可以自定義路由切換時頁面如何滾動。
注意: 這個功能只在支持history.pushState的瀏覽器中可用。
當創(chuàng)建一個Router實例,你可以提供一個scrollBehavior方法:

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滾動到哪個的位置
  }
})

scrollBehavior方法接收tofrom路由對象。第三個參數(shù) savedPosition 當且僅當popstate導(dǎo)航 (通過瀏覽器的 前進/后退 按鈕觸發(fā)) 時才可用。
這個方法返回滾動位置的對象信息,長這樣:

  • { x: number, y: number }
  • { selector:string,offset?:{ x:number,y:number }} (offset只在2.6.0+支持)

如果返回一個falsy (falsy不是 false)的值,或者是一個空對象,那么不會發(fā)生滾動。

scrollBehavior (to, from, savedPosition) {
  return { x: 0, y: 0 }
}

對于所有路由導(dǎo)航,簡單地讓頁面滾動到頂部。
返回savedPosition,在按下后退/前進按鈕時,就會像瀏覽器的原生表現(xiàn)那樣。

scrollBehavior (to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    return { x: 0, y: 0 }
  }
}

模擬滾動到錨點的行為。

scrollBehavior (to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash
    }
  }
}

我們還可以利用路由元信息更細顆粒度地控制滾動。

異步滾動

2.8.0新增

你也可以返回一個Promise來得出預(yù)期的位置描述:

scrollBehavior (to, from, savedPosition) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ x: 0, y: 0 })
    }, 500)
  })
}

將其掛載到從頁面級別的過渡組件的事件上,令其滾動行為和頁面過渡一起良好運行是可能的。

路由懶加載

當打包構(gòu)建應(yīng)用時,Javascript包會變得非常大,影響頁面加載。如果我們能把不同路由對應(yīng)的組件分割成不同的代碼塊,然后當路由被訪問的時候才加載對應(yīng)組件,這樣就更加高效了。
結(jié)合Vue的異步組件和Webpack的代碼分割功能,輕松實現(xiàn)路由組件的懶加載。
首先,可以將異步組件定義為返回一個Promise的工廠函數(shù) (該函數(shù)返回的Promise應(yīng)該resolve組件本身):

const Foo = () => Promise.resolve({ /* 組件定義對象 */ })

第二,在 Webpack中,我們可以使用動態(tài)import語法來定義代碼分塊點 (split point):

import('./Foo.vue') // 返回 Promise

注意:如果您使用的是Babel,你將需要添加syntax-dynamic-import插件,才能使Babel可以正確地解析語法。

結(jié)合這兩者,這就是如何定義一個能夠被Webpack自動代碼分割的異步組件。

const Foo = () => import('./Foo.vue')

在路由配置中什么都不需要改變,只需要像往常一樣使用Foo

const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})

把組件按組分塊

有時候我們想把某個路由下的所有組件都打包在同個異步塊(chunk)中。只需要使用命名chunk,一個特殊的注釋語法來提供chunk name。

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

Webpack會將任何一個異步模塊與相同的塊名稱組合到相同的異步塊中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 導(dǎo)航守衛(wèi) 導(dǎo)航守衛(wèi)主要用來通過跳轉(zhuǎn)或取消的方式守衛(wèi)導(dǎo)航。 參數(shù)或查詢的改變并不會觸發(fā)進入/離開的導(dǎo)航守衛(wèi)。 1、全...
    SailingBytes閱讀 1,228評論 1 3
  • 前言 vue-router是什么:是vue.js官方的路由管理器和vue.js的核心深度的集成,讓開發(fā)者更加簡單的...
    GUAN_one閱讀 3,862評論 0 2
  • 學(xué)習(xí)目的 學(xué)習(xí)Vue的必備技能,必須 熟練使用 Vue-router,能夠在實際項目中運用。 Vue-rout...
    _1633_閱讀 92,911評論 3 58
  • VUE Vue :數(shù)據(jù)驅(qū)動的M V Vm框架 m :model(后臺提供數(shù)據(jù)),v :view(頁面),vM(模板...
    wudongyu閱讀 5,528評論 0 11
  • vue-router學(xué)習(xí)筆記 安裝 并且如果在一個模塊化工程中使用它,必須要通過Vue.use()明確地安裝路由功...
    EL_PSY_CONGROO閱讀 674評論 0 0

友情鏈接更多精彩內(nèi)容