Vue 系列 | Vue-Router

注意: 本文所有示例代碼詳見:vue-rouer-demo

1.What | 什麼是Vue Router

Vue Router是Vue.js提供的官方路由管理器,它和Vue.js深度集成,使構建單頁面應用非常方便。

2.Why | 爲什麼要使用Vue Router

大家打開LorneNote個網站,這是我的一個blog網站,採用傳統的開發模式,鼠標右擊,在出現的菜單裏選擇View Page Source 查看資源文件,大家會看到它對應的是一個HTML文件,然後你回到網站裏點擊歸檔欄,再次右擊查看源文件,你會看到整個頁面被重新加載,對應歸檔的HTML文件,即:

  • https://lornenote.com -----> LorneNote HTML文件
  • https://lornenote.com/archives/ -----> 歸檔 | LorneNote HTML文件

也就是說每一個URL對應一個HTML文件,這樣每次切換頁面的時候我們都需要重新加載我們的頁面,非常影響用戶體驗。

然後就誕生了單頁面形式SPA(single page applications)。在單頁面模式下,不管我們訪問什麼頁面,都返回index.html,類似這樣:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="/favicon.ico">
    <title>hello-world</title>
  <link href="/app.js" rel="preload" as="script"></head>
  <body>
    <noscript>
      <strong>We're sorry but hello-world doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  <script type="text/javascript" src="/app.js"></script></body>
</html>

在單文件模式裏,我們依然可以跳到不同的HTML文件,但我們一般不會這樣做,一個index.html就滿足了我們的需求,用戶切換URL的時候,不再是重新加載我們的頁面,而是根據URL的變化,執行相應的邏輯,數據會通過接口的形式返回給我們,而後做頁面更新。Vue Router就是爲了解決這樣的事情,它可以做這些事情:

  • 提供URL和組件之間的映射關係,並在URL變化的時候執行對象邏輯
  • 提供多種方式改變URL的API(URL的變化不會導致瀏覽器刷新)

3.How | Vue Router 如何使用

核心使用步驟

  1. 安裝並導入VueRouter,調用Vue.use(VueRouter),這樣的話可以使用<router-view><router-link>等全局組件
// 安裝命令
$ npm install vue-router

// main.js
import App from './App.vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
  1. 定義路由組件入口
// App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <router-view></router-view>
  </div>
</template>
  1. 使用定義好的路由組件配置路由列表
// routes.js
import HelloWorld from './components/HelloWorld'
const routes = [
    {path: '/', component: HelloWorld, name:'home'}
]

export default routes
  1. 創建路由實例,並通過路由列表初始化路由實例
// main.js
import routes from'./routes'
const router = new VueRouter({
  routes
})
  1. 將創建好的路由實例掛載到跟實例,這樣我們就可以使用this.$route全局鉤子來操作路由管理器爲我們提供的一些屬性。
// main.js
new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

注意:
this.$router代表我們剛剛實例化的全局路由實例,它和我們導入router相同,只是爲了使用方便,不用在組件中導入了。
this.$route,寫法上少了一個r,代表任何組件的當前路由。

路由動態匹配

有時候我們需要映射不同的路由給相同的組件

// routes.js
{ path:'/user/:id', component:User}

// HelloWorld.vue
<router-link to="/user/123">張三-single params</router-link><br>

在User組件中,可以以this.$route.params的形式接收參數id

<template>
    <div>User
        {{ this.$route.params.id }}  // 123
    </div>
</template>

動態匹配是以冒號區隔的,我們可以有多個動態段:

// routes.js
{ path:'/user/:userName/userId/:id', component:User},  

// HelloWorld.vue
<router-link to="/user/王五/userId/789">王五-mutiple params</router-link>

// User.vue
<template>
    <div>User
        {{ this.$route.params }}  //  { "userName": "王五", "id": "789" }
    </div>
</template>

路由routes與path、$route.params三者的關係見下表:

路由模式 匹配path $route.params
/user/:userName/userId/:id /user/王五/userId/789 { “userName”: “王五”, “id”: “789” }

除了$route.parmas,$route還封裝了其他的信息如:
$route.query:用於獲取URL中的查詢,
$route.hash:用於獲取url中#之後的信息(自己寫的路徑帶#,值會包含#,如果是history模式下自動追加的#,則值不包含這個#),如果沒有,值爲空,如果URL中有多個#,從最後一個開始。

我們重寫一下上文的例子:

// HelloWorld.vue
      <router-link to="/user/Query/userId/101?code=123">Query</router-link><br>
      <router-link to="/user/Hash/userId/102#hash=123">Hash</router-link><br>

// User.vue
<template>
    <div>User
        {{ this.$route.query }}  // { "code": "123" }
        {{ this.$route.hash }}  // #hash=123  原路徑:http://localhost:8081/#/user/Hash/userId/102#hash=123
    </div>
</template>

由於vue-router使用 path-to-regexp作爲路徑path的匹配引用,所以我們可以使用正則表達式作爲路徑:

// routes.js
{ path:'/icon-:flower(\\d+).png', component:User},

// HelloWorld.vue
 <router-link to="/icon-flower123">正則匹配</router-link><br>

// User.vue
<template>
    <div>User
        {{ this.$route.hash }}  // /icon-flower123  原路徑:http://localhost:8081/#/icon-flower123
    </div>
</template>

我們可以使用星號(*)來代表通配符,只有*則匹配所有,常代表404頁面,因爲路由的優先級是優先配置優先匹配,相同的組件誰在前面先匹配誰。所以404頁面通常寫在最後一個:

// routes.js
const routes = [
  // ...
  { path:'/user-*', component:User},
  { path:'*', component:User}
]

// HelloWorld.vue
<router-link to="/user-admin">*Admin</router-link><br>
<router-link to="/none">404</router-link><br>

// User.vue--*Admin
<template>
    <div>User
      {{ this.$route.params.pathMatch }} // admin
    </div>
</template>

// User.vue--404
<template>
    <div>User
      {{ this.$route.params.pathMatch }} // /none
    </div>
</template>

如上:
當使用*號路由時,一個參數pathMatch會自動追加給$route.params,它匹配星號後面的部分。

嵌套路由

真實的應用中常常用到組件之間的嵌套,使用vue-router可以很簡單的表達這種嵌套關係,我們可以在子組件中嵌套使用<router-view>,如:

// NestedRoutes.vue
<template>
    <div>
        <a href="/">←返回</a><br>
        <router-link to="/nestedRoutes/profile">profile</router-link><br>
        <router-link to="/nestedRoutes/archive">archive</router-link><br>
        <p>NestedRoutes</p>
        <router-view></router-view>
    </div>
</template>

爲了做嵌套渲染,我們使用children操作符在路由中做結構配置:

// routes.js 嵌套路由
  {path:'/nestedroutes', component: NestedRoutes, name: 'nestedroutes',
   children: [
     {path:'profile', component: resolve => require(['./components/nested-routes/Profile'],resolve)}, // 匹配  /nestedroutes/profile
     {path:'archive', component: resolve => require(['./components/nested-routes/Archive'],resolve)}   // 匹配  /nestedroutes/archive
   ]
  },

嵌套路由以/爲根路徑,拼接路徑的剩餘部分,children其實和routes的最外層一樣,是數組,所以我們可以根據需要繼續嵌套。

當我們訪問/nestedroutes的時候,不會在這裏渲染任何內容,因爲沒有子路由,如果我們想要渲染,可以在路由裏配置一個空的path的子路由:

// 嵌套路由
  {path:'/nestedroutes', component: NestedRoutes, name: 'nestedroutes',
   children: [
     {path:'', component: resolve => require(['./components/nested-routes/NestedRoutesHome'],resolve)},
     {path:'profile', component: resolve => require(['./components/nested-routes/Profile'],resolve)},
     {path:'archive', component: resolve => require(['./components/nested-routes/Archive'],resolve)}
   ]
  },

編程式的導航

除了以<router-link>的標籤形式導航之外,我們還可以使用編程的方式導航,vue-router爲我們提供了對應的實例方法this.$router.push

這個方法會將URL頁面推到歷史堆棧,所以當我們點擊瀏覽器的返回按鈕的時候指向前一個URL。當我們點擊<router-link>的時候,內部其實調用的是router.push(...),所以這兩個形式功能是等同的。

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

location:導航位置,是一個字符串path或位置描述符對象,
第二個和第三個爲可選參數,導航執行完成會執行這兩個參數。
onComplete導航執行完會執行這個參數(所有的異步鉤子都被執行完之後)
使用方式如下:

       <!--path-->
      <button @click="$router.push('programmatic-navigation')">Path</button><br>
       <!--object-->
      <button @click="$router.push({path:'programmatic-navigation'})">Object</button><br>
      <!-- named route-->
      <button @click="$router.push({name:'programmatic-navigation', params: { id:123 }})">NamedRoute</button><br>
       <!--with query  reuslting in /programmatic-navigation?id=123-->
      <button @click="$router.push({path:'programmatic-navigation', query:{ id:123 }})">Query</button><br>

注意:
如果使用了pathparams就被忽略了,但上面的query不會被忽略。所以如果我們要傳遞參數,可以用兩種方式,一種是提供一個name路由,一種是在path裏面手動拼接參數:

// routes.js
{ path:'/programmatic-navigation/params=/:id', component: resolve => require(['./components/programmatic-navigation/ProgrammaticNavigation'],resolve), name: 'programmatic-navigation-params' },

// ProgrammaticNavigation.vue
<!--傳遞參數  返回 /programmatic-navigation/params=/123-->
      <button @click="$router.push({ name:'programmatic-navigation-params', params: { id }})">WidthParmas01</button><br>
      <button @click="$router.push({ path:`/programmatic-navigation/params=/${ id }`})">WidthParmas02</button><br>

這個規則也同樣適用於router-linkto屬性。

如果我們的當前路徑和跳轉路徑相同,那麼我們需要在beforeRouteUpdate方法中響應數據更新:

ProgrammaticNavigation.vue
<template>
    <div>

        <a href="/">←返回</a><br>
        <p>ProgrammaticNavigation</p>
        <pre>{{ JSON.stringify(params) }}</pre>

        <button @click="$router.push({ path:`/programmatic-navigation/params=/${ 456 }`})">WidthParmas03</button><br>
    </div>
</template>

<script>

  export default {
    name: "ProgrammaticNavigation",
    data() {
      return {
        params:this.$route.params
      }
    },
    beforeRouteUpdate (to, from, next) {
      if (to.path == '/programmatic-navigation/params=/456') {
        this.params = to.params
      }
    }
  }
</script>

router.replace
router.replacerouter.push類似,唯一不同的是它不是推送到一個新的歷史頁,而是替換當前頁。

router.go(n)
這個方法和window.history.go(n)類似,n用整數表示,表示在歷史頁中向前後向後走多少頁。

// 後退一頁
router.go(-1)
// 前進一頁
router.go(1)
// 如果沒有,導航失敗,頁面保持不變
router.go(10)

操作History
路由的router.push,router,replacerouter.go對應window.history.pushState, window.history.replaceStatewindow.history.go, 他們模仿的是window.historyAPIs。

Vue Router的導航方法(push, replace, go)在所有的路由模式下都可以工作(historyhashabstract)。

命名路由

有時候給路由一個名字更方便,用法也很簡單:

// routes.js
  {
    path:'/named-routes/:id',
    component: resolve => require(['./components/named-routes/NamedRoutes'],resolve),
    name:'named-routes'
  },

// HelloWorld.vue
<router-link :to="{name: 'named-routes', params: { id:123 }}">named-routes router-link</router-link><br>
<button @click="$router.push({name: 'named-routes', params: { id:123 }})">named-routes router.push</button>

這兩種方式都是對象傳遞,注意to前面要加冒號的,表示內部是對象表達式而不是單純的字符串。

###命名視圖
有時候我們可能需要在同一頁展示多個視圖,而不是做視圖嵌套,比如說主頁面或者是sidebar側邊欄。這個時候我們就會用到命名視圖,也就是說在同一頁面下使用多個<router-view>,給每個<router-view>一個不同的名字,如果沒給名字,默認名爲default

  // routes.js 命名View
  {
    path:'/named-views',
    component: resolve => require(['./components/named-views/NamedViews'],resolve),
    children: [
      {
        path:'',
        components: {
          default:resolve => require(['./components/named-views/ViewA'],resolve),
          b: resolve => require(['./components/named-views/ViewB'],resolve),
          c: resolve => require(['./components/named-views/ViewC'],resolve),
        },
      }
    ]
  },

// NamedViews.vue
<template>
    <div>
        <a href="/">←返回</a><br>
        <p>NamedViews</p>
        <router-view></router-view>
        <router-view name="b"></router-view>
        <router-view name="c"></router-view>
    </div>
</template>

嵌套命名視圖
我們可以在嵌套視圖裏使用命名視圖創造更復雜的佈局。我們來看一個例子:

// NestedNamedViews.vue
<template>
    <div>
        <p>NestedNamedViews</p>
        <ViewNav/>
        <router-view></router-view>
        <router-view name="b"></router-view>
    </div>
</template>

這裏:

  • NestedNamedViews本身是View組件
  • ViewNav是一個常規的組件
  • router-view內部是被嵌套的視圖組件

路由配置這樣來實現:

// routes.js
{ path: '/nested-named-views',
    component: resolve => require(['./components/named-views/NestedNamedViews'], resolve),
    children: [
      {
        path:'profile',
        component: resolve => require(['./components/nested-routes/Profile'],resolve)
      },
      {
        path:'archive',
        components: {
          default:resolve => require(['./components/named-views/ViewA'],resolve),
          b: resolve => require(['./components/named-views/ViewB'],resolve),
        }
      }
    ]
  },

轉發和別名

轉發是我們可以在訪問a的時候跳轉b。
支持路徑和命名訪問兩種形式:

// routes.js
  {
    path:'/orignal01' ,redirect: '/forward'
  },
  {
    path:'/orignal02' ,redirect: {name: 'forward'}
  },

別名是這樣的,如果/b是組件a的別名,那意味着我們訪問/b的時候,匹配的組件依然是a

// routes.js
{
    path: '/forward',
    name: 'forward',
    component: resolve => require(['./components/redirect-views/RedirectViews'], resolve),
    alias: '/alias'
  },

傳遞Props給路由組件

爲了使路由的組件更加靈活,vue支持在路由組件中傳遞Props,使用props操作符。支持三種模式:
布爾值模式
props設置爲true的時候,route.params將被設置作爲組件的props。

// routes.js
{
    path: '/props-to-route/:id',  // http://10.221.40.28:8080/props-to-route/123
    component: resolve => require(['./components/props-route/PropsRoute'], resolve),
    props: true
  },

對象模式
當props是一個對象的時候,這個對象也一樣會被設置給組件的props,這在props爲固定值的時候很有用:

// routes.js
  {
    path: '/static',  // http://10.221.40.28:8080/static
    component: resolve => require(['./components/props-route/PropsRoute'], resolve),
    props: {name: 'static'}
  },

函數模式
我們可以創建一個函數返回props,這可以讓你轉化參數爲其他類型,經靜態值與基於路由的值結合,等等。

// routes.js
  {
    path: '/function-mode', // http://10.221.40.28:8080/function-mode?keyword=%27beijing%27
    component: resolve => require(['./components/props-route/PropsRoute'], resolve),
    props: (route) => ({ query: route.query.keyword})
  },

如果URL爲/function-mode?keyword='beijing'將傳遞{query: 'beijing'}作爲組件的props。

注意:
props函數是無狀態的,僅僅計算路由的改變。如果需要狀態去定義props,那麼Vue官方建議封裝一個組件,這樣vue就能夠對狀態做出反應。

路由進階

導航警衛

這是vue-router提供的一些控制路由進程的函數,有三種表現方式:全局定義,路由內定義和組件中定義。

全局定義
分爲前置警衛、解析警衛和後置鉤子。我們依次看一下:

1.前置警衛

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

無論哪個導航先觸發之前都會先調用它,警衛的解析可能是異步的,所以在所有的鉤子解析完成之前,導航處於懸停狀態(pending)。實踐中經常在這裏判斷是否攜帶了進入頁面的必要信息,否則做跳轉。(比如通過URL地址欄手動輸入地址非法進入子頁面,需要跳轉到登錄頁讓用戶登錄)

注意:別忘了寫next函數,否則鉤子函數將不會被解析。

2.全局解析警衛

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

和前置警衛一樣,只不過是在導航確認之前,所有組件內警衛和異步路由組件被解析之後會立即調用。

3.全局後置鉤子
這些鉤子和警衛不同的是沒有next函數,並且不影響導航。

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

路由獨享的警衛
我們可以在路由的配置對象裏配置:beforeEnter

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

這個和全局前置警衛作用相同。

組件內警衛
在組件內可以定義一些路由導航警衛(會被傳遞給路由配置表):

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave

beforeRouteEnter不能訪問this,因爲組件還沒有被創建。然後我們可以在next回調裏訪問,在導航被確認時,組件實例會被當做參數傳遞給這個回調:

beforeRouteEnter (to, from, next) {
  next(vm => {
    // access to component instance via `vm`
  })
}

而在beforeRouteUpdatebeforeRouteLeavethis已經可用。所以next回調沒有必要因此也就不支持了。

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

離開警衛常常用於阻止用戶意外離開未經保存的內容,導航可用通過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)
  }
}

完整的導航解析流程

  1. 導航被觸發
  2. beforeRouteLeave警衛在失活組件被調用
  3. 全局的beforeEach警衛被調用
  4. 在可複用的組件內調用beforeRouteUpdate
  5. 在路由配置中調用beforeEnter
  6. 解析異步路由組件
  7. 在激活組件中調用beforeRouterEnter
  8. 調用全局beforeResolve警衛
  9. 導航被確認
  10. 調用全局afterEach鉤子
  11. DOM更新被觸發
  12. 用創建好的實例調用beforeRouteEnter守衛中傳給next的回調函數。

路由Meta字段

定義路由的時候可以包含一個meta字段:

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

怎麼樣使用這個字段呢?

首先,在routes配置裏的每一個路由對象稱爲路由記錄。路由可能會被嵌套,因此當一個路由被匹配的時候,可能會匹配超過一個記錄。

例如,上面的路由配置,/foo/bar將匹配父路由記錄和子路由記錄兩個路由配置。

所有的被匹配的路由記錄都被導出在$route對象(也在導航警衛的路由對象裏)作爲$route.matched數組。因此我們需要迭代這個數組來找到路由記錄裏的meta字段。

在全局導航警衛中檢查meta字段的一個例子:

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() // make sure to always call next()!
  }
})

過渡

簡單說就是給路由加一些過渡效果。有幾種方式可以實現:
1.在統一的router-view入口給一個一致的過渡

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

查看所有的過渡APIs

2.在每一個組件內部給一個特別的過渡

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

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

3.還可以根據當前路由和目標路由之間的關係設計動態過渡

<template>
    <div>
        <div>
            <router-link to="subviewa">subviewa</router-link><br>
            <router-link to="subview-b">subview-b</router-link><br>
            <p>Home</p>
            <transition :name="transitionName">
                <router-view></router-view>
            </transition>
        </div>
    </div>
</template>

<script>
  export default {
    name: "Home",
    data() {
      return {
        transitionName: 'slide-left'
      }
    },
    beforeRouteUpdate (to, from, next) {
      this.transitionName = to.path < from.path ? 'slide-right' : 'slide-left'
      next()
    },
  }
</script>

<style scoped>
    .slide-left-enter, .slide-right-leave-active {
        opacity: 0;
        transform: translateX(10px);
    }
    .slide-left-leave-active, .slide-right-enter {
        opacity: 0;
        transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
    }
</style>

數據獲取

在實踐中,很多時候在進入一個新的路由頁面時,我們都是需要從服務器請求數據的,有兩種方式:

  • 在導航之後獲取:首先導航到新的頁面,在導航的生命週期鉤子中(比如created方法)做數據獲取。
  • 在導航之前獲取:在路由導航進入警衛之前獲取數據,數據獲取完成之後執行導航。

技術上都可以實現,用哪種取決於用戶體驗的目標。

導航之後獲取數據
使用這種方式,我們會立即導航到新的頁面,渲染組件,在組件的created鉤子中渲染組件。當獲取數據是我們可以展示一個loading的狀態,並且每一頁可以有自己的loading視圖。

我們看一個代辦事項的例子:

<template>
    <div>
        <div v-if="loading">
            Loading
        </div>
        <div v-if="error">
            {{ error }}
        </div>
        <div v-if="list" >
            <div v-for=" item in list" :key="item.id">
                <p>{{ item.title }}</p>
            </div>
        </div>
    </div>
</template>

<script>
import  TodoAPI  from '../api/todo';
    export default {
        name: "TodoList",
        data () {
            return {
                loading: false,
                list: null,
                error: null
            }
        },
        created () {
            // 組件創建時獲取數據
            this.fetchData()
        },
        watch: {
            // 路由改變的時候再次調用方法
            '$route': 'fetchData'
        },
        methods: {
            fetchData () {
                this.error = this.list = null
                // 模擬網絡請求
                TodoAPI.getTodoList(
                    (todolist) => {
                    this.loading = false,
                    this.list = todolist
                },
                (error)=>{
                    this.loading = false,
                    this.error = error
                })
            }
        }
    }
</script>

// 網絡請求API -- todo.js
const _todoList = [
    {"id": 1, "title": "購物"},
    {"id": 2, "title": "回覆郵件" }
]

export default ({
    getTodoList (cb,errorCb) {
        setTimeout(() => {
            Math.random() > 0.5
            ? cb(_todoList)
            : errorCb("數據請求失敗")
        },1000)
    }
})

滾動行爲

當我們使用vue-router的時候,我們可能想要打開新頁在某一個位置,或使返回歷史頁保持在上次瀏覽的位置。vue-router允許我們自定義導航行爲。

注意
這個功能僅僅在瀏覽器支持history.pushState的時候可以用。

當我們創建一個路由實例的時候,我們可以創建一個scrollBehavior函數。

const scrollBehavior = function (to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    const postion = {}

    if (to.hash) {
      postion.selector = to.hash
      if (to.hash === '#anchor2') {
        postion.offset = {y: 100 }
      }
      if (/^#\d/.test(to.hash) || document.querySelector(to.hash)) {
        return postion
      }
      return false
    }

    return new Promise(resolve => {
      if (to.matched.some(m => m.meta.scrollToTop)) {
        postion.x = 0
        postion.y = 0
      }

      this.app.$root.$once('triggerScroll', () => {
        resolve(postion)
      })
    })
  }
}

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

這裏面接收to,from兩個路由對象,第三個參數savedPosition,第三個參數僅僅在popstate導航中可用(瀏覽器的向前/後退按鈕被觸發的時候)。

這個對象返回滾動位置對象,格式如:

  • {x: nubmer, y:number }
  • {selector: string, offset? : { x: number, y: number}}(offset 僅僅被支持在2.6.0+以上)

如果是空值或無效值,則不會發出滾動。

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

如果返回savedPosition,和我們使用瀏覽器的向前向後按鈕一下,如果有歷史滾動頁,會返回到歷史頁查看的位置,如果返回{ x: 0, y: 0 }則滾動到頂部。

在開始的例子裏,我們可以這樣模擬滾動到某處的行爲:

if (to.hash) {
      postion.selector = to.hash

      if (to.hash === '#anchor2') {
        postion.offset = {y: 100 }
      }

     // ...
    }

以下我們返回了一個Promise,來返回滾動的位置

return new Promise(resolve => {
// 如果匹配scrollToTop,我們就返回頂部
      if (to.matched.some(m => m.meta.scrollToTop)) {
        postion.x = 0
        postion.y = 0
      }

      // 如果空值或無效值,返回當前滾動位置
      this.app.$root.$once('triggerScroll', () => {
        resolve(postion)
      })
    })

懶加載路由

當使用一個捆綁器構建應用時,JavaScript包會變得非常大,因此影響頁面加載的時間。如果我們可以把每一個路由組件分離單獨的塊,等瀏覽頁面時再加載他們會效率比較高。

Vue的異步組件功能webpack的代碼分離功能可以很容易的做到這一點。

首先,可以將異步組件定義爲返回Promise的工廠函數(應解析爲組件本身):

const Foo = () => Promise.resolve({ /* component definition */ })

其次,在webpack2,我們可以使用動態導入語法去表示代碼分離點:

import('./Foo.vue') // returns a Promise

注意
如果你使用Babel,你需要添加syntax-dynamic-import以便Babel可以正確解析。

把這兩個步驟聯合起來,我們就可以通過webpack自定定義一個異步的代碼分離組件,所以我們可以把我們的路由配置修改爲:

{
    path: '/lazy-route', component: () => import('./components/lazy-route/LazyHome')
  }

在同一個塊中分組組件

有時,我們想要把所有組件分組到相同的異步塊中,爲了實現這個,在webpack > 2.4中,我們可以使用特殊的註釋語法提供命名塊:

{
    path: '/lazyTwo', component: () => import(/* webpackChunkName: "lazy" */ './components/lazy-route/LazyTwo'),
    children: [
      {path: 'lazyThree', component: () => import (/* webpackChunkName: "lazy" */ './components/lazy-route/LazyThree')}
    ]
  },

webpack將使用相同塊名稱的任何異步模塊分組到相同的異步塊中。


參考:Vue Router

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章