天天看點

vue-router詳解及執行個體

寫在前面

使用者進行了互動操作,現在要對頁面内容進行變更,可以通過javascript進行動态替換DOM,但是其不便于分享、收藏,對于搜尋引擎和使用者來說都是不友好的!

什麼是前端路由?

​ 根據不同的 url 位址展示不同的内容或頁面,無需依賴伺服器根據不同URL進行頁面展示操作

優點

  • 使用者體驗好,不需要每次都從伺服器全部擷取,快速展現給使用者

缺點

  • 使用浏覽器的前進,後退鍵的時候會重新發送請求,沒有合理地利用緩存
  • 單頁面無法記住之前滾動的位置,無法在前進,後退的時候記住滾動的位置

簡介

​ 使用 Vue.js ,可以通過組合元件來組成應用程式,當你要把 vue-router 添加進來,我們需要做的是,将元件(components)映射到路由(routes),然後告訴 vue-router 在哪裡渲染它們。

// 1. 定義、引用(路由)元件。
const Foo = { template: '<div>foo</div>' }
import Bar from '@/views/bar.vue'

// 2. 定義路由
const routes = [
  { path: '/foo', name: 'foo', component: Foo },
  { path: '/bar', name: 'bar', component: Bar }
]

// 3. 建立 router 執行個體,然後傳 `routes` 配置
const router = new VueRouter({
  routes // (縮寫)相當于 routes: routes
})

// 4. 建立和挂載根執行個體。通過 router 配置參數注入路
const app = new Vue({
  router
}).$mount('#app')           

複制

動态路由比對

兩種方式傳遞

$route.params

$route.query

模式 比對路徑 擷取參數(路由資訊對象)
/user/:username /user/ligang $route.params.username
/user?:username /user?username=ligang $route.query.username

響應路由參數的變化

​ 當使用路由參數時,例如從

/user/ligang

導航到

user/lg

,原來的元件執行個體會被複用。因為兩個路由都渲染同個元件,比起銷毀再建立,複用則顯得更加高效。不過,這也意味着元件的生命周期鈎子不會再被調用。

方式一:簡單地watch(監測變化)$route對象

watch: {
    '$route' (to, from) {
      // 對路由變化作出響應...
    }
  }           

複制

方式二:使用 2.2 中引入的

beforeRouteUpdate

守衛

beforeRouteUpdate (to, from, next) {
  // 對路由變化作出響應...不要忘記調用next()
}           

複制

示例:新增和編輯使用同一子產品,從編輯切換到新增頁面資訊不會更新!

{
  path: 'add',
  name: 'setting-user-manager-add',
  component: () => import('@/views/setting/user-manager/add-edit.vue'),
  meta: {name: '使用者新增'}
}, {
  path: 'edit',
  name: 'setting-user-manager-edit',
  component: () => import('@/views/setting/user-manager/add-edit.vue'),
  meta: {
      name: '使用者編輯',
      hidden: true
  }
}           

複制

嵌套路由

routes: [{ 
    path: '/user/:id', 
    component: User,
    children: [
        // 比對 /user/:id
        { path: '', component: UserHome },
        // 比對 /user/:id/profile
        { path: 'profile', component: UserProfile },
        // 比對 /user/:id/posts
        { path: 'posts', component: UserPosts }
    ]
}]           

複制

要注意,以 / 開頭的嵌套路徑會被當作根路徑。 這讓你充分的使用嵌套元件而無須設定嵌套的路徑。

程式設計式導航

router.push(location, onComplete?, onAbort?)

聲明式 程式設計式
<router-link :to="..."> router.push(...)
// 方式一:字元串路徑
router.push('/user')
// 方式二:path對象
router.push({ path: '/user' })
// 方式三:路由名稱
router.push({ name: 'user'})           

複制

注意:如果提供了 path,params 會被忽略,query不會!!

// 不生效
router.push({ path: '/user', params: { id: 1 }})
// params生效 /user/1
router.push({ name: 'user', params: { id: 1 }}) // 使用name方式
router.push({ path: `/user/1` }) // 直接在path上擴充
// query不受影響 /user?id=1
router.push({ path: '/user', query: { id: 1 }})           

複制

router.replace(location, onComplete?, onAbort?)

聲明式 程式設計式
<router-link :to="..." replace> router.replace(...)

router.push

很像,唯一的不同就是,它不會向 history 添加新記錄!

router.go(n)

在 history 記錄中向前或者後退多少步,類似

window.history.go(n)

命名視圖

多個非嵌套視圖展示,例如建立一個布局,有

header

頭資訊、

sidebar

(側導航) 和

main

(主内容) 兩個視圖。

<router-view class="view header" name="header"></router-view>
<router-view class="view sidebar" name="sidebar"></router-view>
<router-view class="view main"></router-view>           

複制

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: mainComponent,
        sidebar: sidebarComponent,
        header: headerComponent
      }
    }
  ]
})           

複制

重定向和别名

重定向

// 方式一:字元串路徑path
{ path: '/a', redirect: '/b' }
// 方式二:name
{ path: '/a', redirect: {name: 'b'} }
// 方式三:動态傳回重定向目标
{ path: '/a', redirect: to => {
  /* 方法接收 目标路由 作為參數;return 重定向的 字元串路徑/路徑對象 */
}}           

複制

别名

/a

的别名是

/b

,意味着當使用者通路

/b

時,URL會保持為

/b

,但是路由比對則為

/a

,就像使用者通路

/a

一樣。

{ path: '/a', component: A, alias: '/b' }           

複制

『别名』的功能讓你可以自由地将 UI 結構映射到任意的 URL,而不是受限于配置的嵌套路由結構。

示例:上述【動态路由比對】可修改成如下,可能存在name問題

{
  path: 'add',
  name: 'setting-user-manager-add',
  component: () => import('@/views/setting/user-manager/add-edit.vue'),
  meta: {name: '使用者新增'},
  alias: 'edit'
}           

複制

向路由元件傳遞 props

路由元件傳參

預設(正常)方式:通過$route.params擷取

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
  routes: [{ path: '/user/:id', component: User }]
})           

複制

使用props解耦:隻需要将props設定為true

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
  routes: [{ path: '/user/:id', component: User, props: true }]
})           

複制

注意:上述props不僅可以設定為布爾值,還可以設定為對象或函數,具體請檢視:「https://router.vuejs.org/zh-cn/essentials/passing-props.html」

HTML5 History 模式

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

複制

需要背景配置,否則輸入的除首頁外都為404(當然系統内跳轉可以)。具體ngix、Apache、node等配置參考:「https://router.vuejs.org/zh-cn/essentials/history-mode.html」

這裡說一下本地webpack需要增加的配置情況:

historyApiFallback: true

「https://doc.webpack-china.org/configuration/dev-server/#devserver-historyapifallback」vue-cli生成的預設webpack配置,

historyApiFallback: {
  rewrites: [
    { from: /.*/, to: path.join(config.dev.assetsPublicPath, 'index.html') },
  ],
}           

複制

在window下特定node版本會有問題!

導航守衛

『導航』表示路由正在發生改變

導航守衛主要用來通過跳轉或取消的方式守衛導航。注意參數或查詢的改變并不會觸發進入/離開的導航守衛。可以通過觀察

$route

對象來應對這些變化,或使用

beforeRouteUpdate

的元件内守衛。

全局守衛

使用

router.beforeEach

注冊一個全局前置守衛

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

複制

守衛是異步解析執行,此時導航在所有守衛 resolve 完之前一直處于 等待中。是以確定要調用 next 方法,否則鈎子就不會被 resolved。

全局解析守衛

在 2.5.0+ 你可以用

router.beforeResolve

注冊一個全局守衛。這和

router.beforeEach

類似,差別是在導航被确認之前,同時在所有元件内守衛和異步路由元件被解析之後,解析守衛就被調用。

全局後置鈎子

你也可以注冊全局後置鈎子,然而和守衛不同的是,這些鈎子不會接受

next

函數也不會改變導航本身:

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

複制

路由獨享的守衛

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

複制

元件内的守衛

  • beforeRouteEnter

  • beforeRouteUpdate

    (2.2 新增)
  • beforeRouteLeave

需要注意的是beforeRouteEnter不能通路this,可以通過傳一個回調給

next

來通路元件執行個體。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通過 `vm` 通路元件執行個體
  })
}           

複制

完整的導航解析流程

  1. 導航被觸發。
  2. 在失活的元件内調用離開守衛

    beforeRouteLeave

  3. 調用全局的

    beforeEach

    守衛。
  4. 在重用的元件内調用

    beforeRouteUpdate

    守衛 (2.2+)。
  5. 在路由配置裡調用獨享守衛

    beforeEnter

  6. 解析異步路由元件。
  7. 在被激活的元件内調用

    beforeRouteEnter

  8. 調用全局的

    beforeResolve

    守衛 (2.5+)。
  9. 導航被确認。
  10. 調用全局的

    afterEach

    鈎子。
  11. 觸發 DOM 更新。
  12. 用建立好的執行個體調用

    beforeRouteEnter

    守衛中傳給

    next

    的回調函數。

路由元資訊

meta

字段來設定名稱、是否需要驗證、是否隐藏等附加資訊!!

一個路由比對到的所有路由記錄會暴露為

$route

對象(還有在導航守衛中的路有對象)的

$route.matched

數組。是以,我們需要周遊

$route.matched

來檢查路由記錄中的

meta

字段。

if (to.meta.requireAuth) {   // 判斷該路由是否需要登入權限
    if (store.state.token) {  // 通過vuex state擷取目前的token是否存在
        next();
    }else {
        next({
            path: '/login',
            query: {redirect: to.fullPath}  // 将跳轉的路由path作為參數,登入成功後跳轉到該路由
        })
    }
}else {
    next();
}           

複制

資料擷取

有時候,進入某個路由後,需要從伺服器擷取資料。

  • 導航完成之後擷取:先完成導航,然後在接下來的元件生命周期鈎子中擷取資料。在資料擷取期間顯示『加載中』之類的訓示。

    該方式會馬上導航和渲染元件,然後在元件的

    created

    鈎子中擷取資料。這讓我們有機會在資料擷取期間展示一個 loading 狀态,還可以在不同視圖間展示不同的 loading 狀态。
  • 導航完成之前擷取:導航完成前,在路由進入的守衛中擷取資料,在資料擷取成功後執行導航。

    該方式在導航轉入新的路由前擷取資料。我們可以在接下來的元件内的

    beforeRouteEnter

    守衛中擷取資料,當資料擷取成功後隻調用

    next

    方法。

滾動行為

隻在 HTML5 history 模式下可用。當切換到新路由時,想要頁面滾到頂部,或者是保持原先的滾動位置,就像重新加載頁面那樣。

vue-router

能做到,而且更好,它讓你可以自定義路由切換時頁面如何滾動。

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

複制

參考位址:「https://router.vuejs.org/zh-cn/advanced/scroll-behavior.html」

特别說明

Router 執行個體

屬性 說明
router.app router 的 Vue 根執行個體
router.mode 路由使用的模式
router.currentRoute 目前路由對應的路由資訊對象

方法:router.beforeEach(guard)、router.beforeResolve(guard) 、router.afterEach(hook)、router.push(location, onComplete?, onAbort?)、router.replace(location, onComplete?, onAbort?)、router.go(n)、router.back()、router.forward()、router.getMatchedComponents(location?)、router.resolve(location, current?, append?)、router.addRoutes(routes)、router.onReady(callback, [errorCallback])、router.onError(callback)

路由資訊對象

每次成功的導航後都會産生一個新的對象

  • 在元件内,即

    this.$route

  • $route

    觀察者回調内
  • router.match(location)

    的傳回值
  • 導航守衛的參數:

    router.beforeEach((to, from, next) => { // to 和 from 都是 路由資訊對象 })

  • scrollBehavior

    方法的參數:

    const router = new VueRouter({ scrollBehavior (to, from, savedPosition) { // to 和 from 都是 路由資訊對象 } })

    其包含的屬性值:route.path、route.path、route.params、route.query、route.query、route.hash、route.fullPath、route.fullPath、route.matched

重點強調:this.router.currentRoute===this.router.currentRoute === this.route

行為表現

因為它也是個元件,是以可以配合

<transition>

<keep-alive>

使用。如果兩個結合一起用,要確定在内層使用

<keep-alive>

<transition>
  <keep-alive>
    <router-view></router-view>
  </keep-alive>
</transition>           

複制

router-link

<router-link>

比起寫死的

<a href="...">

會好一些,理由如下:

  • 無論是 HTML5 history 模式還是 hash 模式,它的表現行為一緻,是以,當你要切換路由模式,或者在 IE9 降級使用 hash 模式,無須作任何變動。
  • 在 HTML5 history 模式下,

    router-link

    會守衛點選事件,讓浏覽器不再重新加載頁面。
  • 當你在 HTML5 history 模式下使用

    base

    選項之後,所有的

    to

    屬性都不需要寫(基路徑)了。

    base相關說明:「https://router.vuejs.org/zh-cn/api/options.html#base」

執行個體

header編寫

第一步:擷取router的全部配置資訊

import {ROUTES} from '@/app.router'

,然後循環鋪值(擷取一級的路由)

meta.name

第二步:選擇header,路由跳轉;主要思路:在一級元件上配置

meta.defaultRouteName

資訊,擷取該資訊後,進行調整(如果不含有該資訊,則預設第一個子路由)

第三步:處理目前選中的的header項目

watch: {
  '$route': {
    // 必須,解決路由同步加載元件時,$watch首次不執行的問題
    immediate: true,
    handler (to) {
      if (to.matched[0]) {
        this.currentRoute = to.matched[0].name
      }
    }
  }
}           

複制

sidebar編寫

基本思路和header相似,唯一差別是要根據目前一級路由進行鋪值!