vue-router使用詳情

vue-router實現原理

SPA(single page application):單一頁面應用程序,只有一個完整的頁面;它在加載頁面時,不會加載整個頁面,而是只更新某個指定的容器中內容。單頁面應用(SPA)的核心之一是:更新視圖而不重新請求頁面;vue-router在實現單頁面前端路由時,提供了兩種方式:Hash模式和History模式。

1、hash模式

隨著 ajax 的流行,異步數據請求交互運行在不刷新瀏覽器的情況下進行。而異步交互體驗的更高級版本就是 SPA —— 單頁應用。單頁應用不僅僅是在頁面交互是無刷新的,連頁面跳轉都是無刷新的,為了實現單頁應用,所以就有了前端路由。類似于服務端路由,前端路由實現起來其實也很簡單,就是匹配不同的 url 路徑,進行解析,然后動態的渲染出區域 html 內容。但是這樣存在一個問題,就是 url 每次變化的時候,都會造成頁面的刷新。那解決問題的思路便是在改變url的情況下,保證頁面的不刷新。在 2014 年之前,大家是通過 hash 來實現路由,url hash 就是類似于:

http://www.xxx.com/#/login

這種 #。后面 hash 值的變化,并不會導致瀏覽器向服務器發出請求,瀏覽器不發出請求,也就不會刷新頁面。另外每次 hash 值的變化,還會觸發hashchange 這個事件,通過這個事件我們就可以知道 hash 值發生了哪些變化。然后我們便可以監聽hashchange來實現更新頁面部分內容的操作:

function matchAndUpdate () {
   // todo 匹配 hash 做 dom 更新操作
}

window.addEventListener('hashchange', matchAndUpdate)

2、history 模式

14年后,因為HTML5標準發布。多了兩個 API,pushState 和 replaceState,通過這兩個 API 可以改變 url 地址且不會發送請求。同時還有popstate事件。通過這些就能用另一種方式來實現前端路由了,但原理都是跟 hash 實現相同的。用了HTML5的實現,單頁路由的url就不會多出一個#,變得更加美觀。但因為沒有 # 號,所以當用戶刷新頁面之類的操作時,瀏覽器還是會給服務器發送請求。為了避免出現這種情況,所以這個實現需要服務器的支持,需要把所有路由都重定向到根頁面。

function matchAndUpdate () {
   // todo 匹配路徑 做 dom 更新操作
}

window.addEventListener('popstate', matchAndUpdate)

vue-router的使用

1、動態路由匹配

我們經常需要把某種模式匹配到的所有路由,全都映射到同個組件。例如,我們有一個 User 組件,對于所有 ID 各不相同的用戶,都要使用這個組件來渲染。那么,我們可以在 vue-router 的路由路徑中使用“動態路徑參數”(dynamic segment) 來達到這個效果

const User = {
  template: '<div>User</div>'
}

const router = new VueRouter({
  routes: [
    // 動態路徑參數 以冒號開頭
    { path: '/user/:id', component: User }
  ]
})

現在呢,像 /user/foo 和 /user/bar 都將映射到相同的路由。

一個“路徑參數”使用冒號 : 標記。當匹配到一個路由時,參數值會被設置到 this.$route.params,可以在每個組件內使用。于是,我們可以更新 User 的模板,輸出當前用戶的 ID:

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

你可以在一個路由中設置多段『路徑參數』,對應的值都會設置到 $route.params 中。例如:

image

提醒一下,當使用路由參數時,例如從/user/foo導航到/user/bar,原來的組件實例會被復用。因為兩個路由都渲染同個組件,比起銷毀再創建,復用則顯得更加高效。不過,這也意味著組件的生命周期鉤子不會再被調用。復用組件時,想對路由參數的變化作出響應的話,你可以簡單地 watch (監測變化) $route 對象

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

或者使用 2.2 中引入的 beforeRouteUpdate 守衛:

const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
}

2、嵌套路由

實際生活中的應用界面,通常由多層嵌套的組件組合而成。同樣地,URL 中各段動態路徑也按某種結構對應嵌套的各層組件,例如

image

借助 vue-router,使用嵌套路由配置,就可以很簡單地表達這種關系

<div id="app">
  <router-view></router-view>
</div>
const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})

這里的<router-view>是最頂層的出口,渲染最高級路由匹配到的組件。同樣地,一個被渲染組件同樣可以包含自己的嵌套 <router-view>。例如,在 User 組件的模板添加一個 <router-view>

const User = {
  template: `
    <div class="user">
      <h2>User {{ $route.params.id }}</h2>
      <router-view></router-view>
    </div>
  `
}

要在嵌套的出口中渲染組件,需要在 VueRouter 的參數中使用 children 配置:

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          // 當 /user/:id/profile 匹配成功,
          // UserProfile 會被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 當 /user/:id/posts 匹配成功
          // UserPosts 會被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

3、編程式導航

注意:在 Vue 實例內部,你可以通過 $router訪問路由實例。因此你可以調用 this.$router.push

image
參數可以是一個字符串路徑,或者一個描述地址的對象:

// 字符串
router.push('home')

// 對象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})

// 帶查詢參數,變成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 這里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

//router.go(n)這個方法的參數是一個整數,意思是在 history 記錄中向前或者后退多少步,類似 window.history.go(n)
// 在瀏覽器記錄中前進一步,等同于 history.forward()
router.go(1)

// 后退一步記錄,等同于 history.back()
router.go(-1)

// 前進 3 步記錄
router.go(3)

// 如果 history 記錄不夠用,那就默默地失敗唄
router.go(-100)
router.go(100)

4、命名視圖

有時候想同時 (同級) 展示多個視圖,而不是嵌套展示,例如創建一個布局,有 sidebar (側導航) 和 main (主內容) 兩個視圖,這個時候命名視圖就派上用場了。你可以在界面中擁有多個單獨命名的視圖,而不是只有一個單獨的出口。如果 router-view 沒有設置名字,那么默認為 default。

//name對應的是組件名字
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

一個視圖使用一個組件渲染,因此對于同個路由,多個視圖就需要多個組件。確保正確使用 components 配置 (帶上 s):

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,//Foo是組件名字
        a: Bar,//Bar是組件名字
        b: Baz//Baz是組件名字
      }
    }
  ]
})

嵌套命名視圖

UserSettings 組件的<template>部分應該是類似下面的這段代碼:

<!-- UserSettings.vue -->
<div>
  <h1>User Settings</h1>
  <NavBar/>
  <router-view/>
  <router-view name="helper"/>
</div>

然后你可以用這個路由配置完成該布局:

{
  path: '/settings',
  // 你也可以在頂級路由就配置命名視圖
  component: UserSettings,
  children: [{
    path: 'emails',
    component: UserEmailsSubscriptions
  }, {
    path: 'profile',
    components: {
      default: UserProfile,
      helper: UserProfilePreview
    }
  }]
}

5、重定向與別名

重定向也是通過 routes 配置來完成,下面例子是從 /a 重定向到 /b:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})

重定向的目標也可以是一個命名的路由:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

甚至是一個方法,動態返回重定向目標:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目標路由 作為參數
      // return 重定向的 字符串路徑/路徑對象
    }}
  ]
})

別名:/a 的別名是 /b,意味著,當用戶訪問 /b 時,URL 會保持為 /b,但是路由匹配則為 /a,就像用戶訪問 /a 一樣。

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

總結:

  • 『重定向』的意思是:當用戶訪問/a時,URL將會被替換成/b,然后匹配路由為/b。(換藥換湯)
  • 別名的意思是:/a的別名是/b,意味著,當用戶訪問/b時,URL會保持為/b,但是路由匹配則為/a,就像用戶訪問/a 一樣。(換湯不換藥)

6、導航守衛

當做Vue-cli項目的時候感覺在路由跳轉前做一些驗證,比如登錄驗證,是網站中的普遍需求。對此,vue-router 提供的 beforeEach可以方便地實現全局導航守衛(navigation-guards)。

如何設置一個全局守衛

你可以使用 router.beforeEach 注冊一個全局前置守衛:就是在你router配置的下方注冊

const router = new VueRouter({ ... })

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

當一個導航觸發時,全局前置守衛按照創建順序調用。守衛是異步解析執行,此時導航在所有守衛 resolve 完之前一直處于 等待中。

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

  • to: Route: 即將要進入的目標 路由對象

  • from: Route: 當前導航正要離開的路由

  • next: Function: 一定要調用該方法來 resolve 這個鉤子。執行效果依賴 next 方法的調用參數。

  • next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。

  • next(false): 中斷當前的導航。如果瀏覽器的 URL 改變了 (可能是用戶手動或者瀏覽器后退按鈕),那么 URL 地址會重置到 from 路由對應的地址。

  • next('/') 或者 next({ path: '/' }): 跳轉到一個不同的地址。當前的導航被中斷,然后進行一個新的導航。你可以向 next 傳遞任意位置對象,且允許設置諸如 replace: true、name: 'home' 之類的選項以及任何用在 router-link 的 to prop 或 router.push 中的選項。

  • next(error): (2.4.0+) 如果傳入 next 的參數是一個 Error 實例,則導航會被終止且該錯誤會被傳遞給 router.onError() 注冊過的回調。

確保要調用 next 方法,否則鉤子就不會被 resolved。
舉個例子:

const router = new VueRouter({ ... }) //這是路由配置,我就不多說了

const whiteList = ['/error', '/register/regindex', '/register/userauthent',  '/register/submit'] // 路由白名單
vueRouter.beforeEach(function(to,from,next){
    console.log("進入守衛");
    if (userInfo.user_id>0){
        console.log("登錄成功");
        next();   //記得當所有程序執行完畢后要進行next(),不然是無法繼續進行的;
    }else{
        console.log("登錄失敗");
        getUserInfo.then(res => {
            if(res){
                if (res.user_id){
                    if (res.status == 4) {
                        //賬號凍結
                        next({ path: '/error', replace: true, query: { noGoBack: true } })
                    }
                    if (res.status == 3) {
                        //認證審核中
                        next({ path: '/register/submit', replace: true, query: { noGoBack: true } })
                    }
                    if (res.status != 1 && res.status != 3) {
                        if (!res.mobile ) {
                            next({ path: '/register/regindex', replace: true, query: { noGoBack: true }})
                        } else {
                            //綁定完手機號了
                            next({ path: '/register/userauthent', replace: true, query: { noGoBack: true } })
                        }
                    }
                    next();  //記得當所有程序執行完畢后要進行next(),不然是無法繼續進行的;
                }else{
                    if (whiteList.indexOf(to.path) !== -1) { // 在免登錄白名單,直接進入
                        next();  //記得當所有程序執行完畢后要進行next(),不然是無法繼續進行的;
                    }else{
                        next({ path: '/register/regindex', replace: true, query: { noGoBack: true }})
                    } 
                }
                
            }else{
                
                }
            }
            
        }).catch(()=>{
            //跳轉失敗頁面
            next({ path: '/error', replace: true, query: { noGoBack: true }})
        })
    }
    
    
});

export default router

最后和大家說下如果白名單太多或項目更大時,我們需要把白名單換為vue-router路由元信息:

直接在路由配置的時候,給每個路由添加一個自定義的meta對象,在meta對象中可以設置一些狀態,來進行一些操作。用它來做登錄校驗再合適不過了

{
  path: '/actile',
  name: 'Actile',
  component: Actile,
  meta: {
    login_require: false
  },
},
{
  path: '/goodslist',
  name: 'goodslist',
  component: Goodslist,
  meta: {
    login_require: true
  },
  children:[
    {
      path: 'online',
      component: GoodslistOnline
    }
  ]
}

這里我們只需要判斷item下面的meta對象中的login_require是不是true,就可以做一些限制了

router.beforeEach((to, from, next) => {
  if (to.matched.some(function (item) {
    return item.meta.login_require
  })) {
    next('/login')
  } else 
    next()
})

全局解析守衛(router.beforeResolve)

你可以用router.beforeResolve 注冊一個全局守衛。這和 router.beforeEach 類似,區別是在導航被確認之前,同時在所有組件內守衛和異步路由組件被解析之后,解析守衛就被調用

路由獨享的守衛

你可以在路由配置上直接定義 beforeEnter 守衛:

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

組件內的守衛

你可以在路由組件內直接定義以下路由導航守衛:

  • beforeRouteEnter
  • beforeRouteUpdate
  • 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` 訪問組件實例
  })
}

這個離開守衛通常用來禁止用戶在還未保存修改前突然離開。該導航可以通過 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)
  }
}

完整的導航解析流程

image
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 前言 vue-router是什么:是vue.js官方的路由管理器和vue.js的核心深度的集成,讓開發者更加簡單的...
    GUAN_one閱讀 3,741評論 0 2
  • 學習目的 學習Vue的必備技能,必須 熟練使用 Vue-router,能夠在實際項目中運用。 Vue-rout...
    _1633_閱讀 92,419評論 3 58
  • 回憶: 我們知道,h5的history或者hash幫助我們解決了,變化url跳轉頁面不發送請求,并且我們能監聽到u...
    LoveBugs_King閱讀 3,663評論 0 5
  • Vue八個生命周期 beforeCreate【創建前】created【創建后】 beforeMount【載入前】 ...
    艾薩克菊花閱讀 1,343評論 0 12
  • 有一陣子了。 我清楚地知道,我需要解憂。 而且重在“解”字。 那種對未來不確定的茫然感,那種交流被硬生生中斷的憋堵...
    Walking林楊閱讀 593評論 1 2