注意: 本文所有示例代碼詳見: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 如何使用
核心使用步驟
- 安裝並導入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)
- 定義路由組件入口
// App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<router-view></router-view>
</div>
</template>
- 使用定義好的路由組件配置路由列表
// routes.js
import HelloWorld from './components/HelloWorld'
const routes = [
{path: '/', component: HelloWorld, name:'home'}
]
export default routes
- 創建路由實例,並通過路由列表初始化路由實例
// main.js
import routes from'./routes'
const router = new VueRouter({
routes
})
- 將創建好的路由實例掛載到跟實例,這樣我們就可以使用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>
注意:
如果使用了path
,params
就被忽略了,但上面的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-link
的to
屬性。
如果我們的當前路徑和跳轉路徑相同,那麼我們需要在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.replace
與router.push
類似,唯一不同的是它不是推送到一個新的歷史頁,而是替換當前頁。
router.go(n)
這個方法和window.history.go(n)
類似,n
用整數表示,表示在歷史頁中向前後向後走多少頁。
// 後退一頁
router.go(-1)
// 前進一頁
router.go(1)
// 如果沒有,導航失敗,頁面保持不變
router.go(10)
操作History
路由的router.push
,router,replace
和router.go
對應window.history.pushState
, window.history.replaceState
和 window.history.go
, 他們模仿的是window.historyAPIs。
Vue Router的導航方法(push
, replace
, go
)在所有的路由模式下都可以工作(history
,hash
和abstract
)。
命名路由
有時候給路由一個名字更方便,用法也很簡單:
// 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`
})
}
而在beforeRouteUpdate
和beforeRouteLeave
,this
已經可用。所以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)
}
}
完整的導航解析流程
- 導航被觸發
beforeRouteLeave
警衛在失活組件被調用- 全局的
beforeEach
警衛被調用 - 在可複用的組件內調用
beforeRouteUpdate
- 在路由配置中調用
beforeEnter
- 解析異步路由組件
- 在激活組件中調用
beforeRouterEnter
- 調用全局beforeResolve警衛
- 導航被確認
- 調用全局
afterEach
鉤子 - DOM更新被觸發
- 用創建好的實例調用
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>
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