代碼
官方給出下面的例子在不使用vue-router的狀況下來實現一個路由。
該示例結合了H5歷史管理API、單文件組件、JS模塊相關內容來實現路由javascript
後面說的頁面只是一條瀏覽器歷史記錄,關於歷史記錄管理見歷史記錄管理(window.history)
simple_router.js爲入口,進行組件的加載,和其餘一些功能初始化工做。css
//simple_router.js data: { currentRoute: window.location.pathname }, computed: { ViewComponent() { const matchingView = routes[this.currentRoute] return matchingView ? require('./pages/' + matchingView + '.vue').default : require('./pages/404.vue').default } }, render(h) { return h(this.ViewComponent) }
屬性currentRoute
是一個狀態轉換器(在VLink.vue
中改變狀態),在下層組件中經過改變它來隨時切換頁面。它在轉換頁面前,會去路由表中查找對應的頁面路徑。html
routes.js爲路由表vue
//routes.js export default { '/':'Home', '/about':'About' }
每一個路由的頁面都是一個組件,切換頁面就是在這些組件中切換。要來回切換這些組件須要先加載它們,咱們在ViewComponent
計算屬性中完成這個工做,經過currentRoute
定位好對應的組件,在經過require(組件路徑).default
在運行時動態的加載它,並在渲染函數中使用它。java
特別注意的是,若是你使用vue-loader@1.4+
版本,這個require後面要加上default,不然報個加載模板失敗的錯誤,由於在這個版本及更高版本上,vue
文件導出的全是esModule,而require加載的是commonjs格式的模塊,在這裏具體就是它返回的是一個包含default
的對象的對象。
具體查看vue-loader API esModule選項、ES6入門模塊部分、記升級vue-loader版本時遇到的一個坑webpack
這裏入口中最重要的是別忘了,使用window.onpopstate管理歷史記錄,激活後退按鈕的功能ios
window.addEventListener('popstate', () => { app.currentRoute = window.location.pathname })
以後就是定義須要切換的幾個組件,Home.vue
、About.vue
和404.vue
,它們大同小異git
<!--Home.vue--> <template> <main-layout> <p>Welcome home</p> </main-layout> </template> <script> import MainLayout from '../layouts/Main.vue' export default { components: { MainLayout } } </script>
這些組件經過Main.vue
完成具體的佈局es6
<!--Main.vue--> <template> <div class="container"> <ul> <li> <v-link href="/">Home</v-link> <v-link href="/about">About</v-link> </li> </ul> <slot></slot> </div> </template> <script> import VLink from '../components/VLink.vue' export default { components: { VLink } } </script>
接着就是在Main.vue
中使用子組件VLink.vue
github
<!--VLink.vue--> <template> <a v-bind:href="href" v-bind:class="{ active: isActive }" v-on:click="go" > <slot></slot> </a> </template> <script> import routes from '../routes' export default { props: { href: { type:String, required: true } }, computed: { isActive () { return this.href === this.$root.currentRoute } }, methods: { go (event) { event.preventDefault() this.$root.currentRoute = this.href window.history.pushState( null, routes[this.href], this.href ) } } } </script>
VLink.vue
很也是很關鍵,定義了一個連接,在開始入口文件中的currentRoute
這個狀態轉換器,就是經過點擊連接改變狀態的,而這個改變值this.href
是經過佈局組件Main.vue
Props數據下發下來的(<v-link href="/about">
),最後別忘記把這個新狀態做爲URL推入歷史記錄管理的狀態棧中。
總結下就是 經過simple_router.js
定義如何加載頁面(運行時動態加載),經過VLink.vue
觸發這個加載,經過佈局文件將這兩部分結合在一塊兒。其中夾雜了歷史記錄的管理。
該插件總體結構比較簡單,而細節很是繁瑣(用來解決各類各樣的缺陷和bug)。
由命名路由與子路由構成總體結構,咱們用它構建以下頁面。
目錄結構以下
//router.js export default new VueRouter({ routes: [ { path: '/', component: Home }, { path: '/settings', component: UserSettings, children: [ { path: 'userinfo', component:UserInfo }, { path: 'useremail', component:UserEmail } ] }, { path: '/interaction', component: UserInteraction, children: [ { path: 'userfriend', component: UserFriend }, { path: 'userFollow', component: UserFollow} ] } ] })
頂層路由及其對應的組件
<!-- index.html --> <div id="app"> <h1>vue-router 測試頁</h1> <div class="nav-header"> <router-link to="/">用戶首頁</router-link> <router-link to="/settings">用戶設置</router-link> <router-link to="/interaction">社交管理</router-link> </div> <router-view></router-view> </div>
<!-- UserSettings.vue --> <template> <div class="container"> <ul class="nav-left"> <router-link tag="li" to="/settings/userinfo"><a>基本信息</a></router-link> <router-link tag="li" to="/settings/useremail"><a>更換郵箱</a></router-link> </ul> <div class="main-right"> <router-view></router-view> </div> </div> </template>
<!-- UserInteraction.vue --> <template> <div class="container"> <ul class="nav-left"> <router-link tag="li" to="/interaction/userfriend"><a>個人好友</a></router-link> <router-link tag="li" to="/interaction/userfollow"><a>關注的人</a></router-link> </ul> <div class="main-right"> <router-view></router-view> </div> </div> </template>
第二層路由(子路由)及其對應組件,這裏。
如下兩張圖說明路由和子路由是如何工做的。
第一張圖說明當咱們點擊連接,通過路由就能夠把對應的組件,放到頁面指定的<router-view>
中。而一樣通過子路由就能夠把對應的組件,放到頂層組件中指定的<router-view>
中。
而第二張,就是對路由過程的補充,經過路由或子路由去尋找對應組件,找到的組件再反過來放入視圖中。
有時候咱們須要在一個組件中使用多塊<router-view>
,以便增長組件的重用,就可使用命名路由。
好比咱們須要在用戶設置中的基本信息
和更換郵箱
中都加一段廣告。
增長一個廣告組件Advertisement.vue
<!-- pages/common/Advertisement.vue --> <template> <div class="adver"> <h1>某廣告</h1> </div> </template>
在UserSettings.vue
中添加一段命名的<router-view>
<!-- UserSettings.vue --> <div class="main-right"> <router-view></router-view> <!-- 添加一段視圖 --> <router-view name="adver"></router-view> </div>
在路由文件中爲命名的<router-view>
添加命名路由。default
路由對應那些未命名的<router-view>
//router.js //... children: [ { path: 'userinfo', components: { default:UserInfo, //添加命名路由 adver:Advertisement } }, { path: 'useremail', components: { default:UserEmail, //添加命名路由 adver:Advertisement } } ] //...
具體代碼 添加命名路由
被激活的連接,vue-router會爲其添加樣式類router-link-active
,咱們在這個class中能夠爲其添加具體樣式,上面代碼中已經被添加了樣式。
a.router-link-active, li.router-link-active>a { background-color: gainsboro; color: #37C6C0; }
這裏被分紅兩種狀況
當這種不帶tag
的寫法
<router-link to="/">用戶首頁</router-link>
被解析成
帶tag
的寫法
<router-link tag="li" to="/settings/userinfo"><a>基本信息</a></router-link>
被解析成
類router-link-active
被添加在tag
上。
細心的你能夠發現無論哪一個連接被激活,用戶首頁上始終存在着router-link-active
,這是由於它的路由是/
,因此在其餘路由被解析時,/
也會被匹配。咱們能夠用exact
解決這個問題,它使路徑字符串要徹底匹配。
<router-link to="/" exact>用戶首頁</router-link>
有時候一堆同級路由它們所對應的組件基本相同,咱們就可使用動態路由,匹配到同一個組件。
咱們在個人好友
中有一個好友列表。當點擊某好友會顯示他的信息。
下面只使用了最簡單的匹配模式,關於更復雜的匹配,vue-router使用path-to-regexp
動態匹配請求路徑,具體查看PocketLibs(2)—— 請求相關 path-to-regexp
//router.js //...... { path: '/interaction', component: UserInteraction, children: [ { path: 'userfriend', component: UserFriend, //添加好友信息 爲組件FriendInfo添加動態路由 children: [ { path:'fd/:id',component:FriendInfo} ] }, { path: 'userFollow', component: UserFollow} ] } //......
修改組件UserFriend.vue
,添加好友列表與好友信息視圖
<!-- UserFriend.vue --> <template> <div class="friend-list"> <h3>好友列表</h3> <ul> <li v-for="item in 10" :key="item"> <router-link :to="'/interaction/userfriend/fd/' + item ">好友{{item}}</router-link> </li> </ul> <div> <!--好友信息視圖--> <router-view></router-view> </div> </div> </template>
添加好友信息組件FriendInfo.vue
<!-- FriendInfo.vue --> <template> <div>好友{{$route.params.id}}信息</div> </template>
在剛纔的代碼中,UserFriend.vue
中有如下代碼
<li v-for="item in 10" :key="item"> <router-link :to="'/interaction/userfriend/fd/' + item ">好友{{item}}</router-link> </li>
:to="'/interaction/userfriend/fd/' + item "
有點違和。咱們能夠用命名路由改造一下。
先在路由文件中爲用戶信息對應的路由添加成name
屬性,切換成命名路由。
//router.js { path: 'userfriend', component: UserFriend, //添加好友信息 爲組件FriendInfo添加動態路由 children: [ //爲動態路由添加name { path:'fd/:id',name:'fd',component:FriendInfo} ] }
而後修改UserFriend.vue
中的<router-link>
爲
<!-- UserFriend.vue --> <router-link :to="{name:'fd',params:{id:item}}">好友{{item}}</router-link>
效果與上面的同樣。
咱們再仔細看看組件FriendInfo.vue
<!--FriendInfo.vue--> <template> <div>好友{{$route.params.id}}信息</div> </template>
在組件中使用 $route 會使之與其對應路由造成高度耦合,從而使組件只能在某些特定的 URL 上使用,限制了其靈活性。咱們能夠爲路徑參數添加Props數據下發。
繼續修改好友信息的路由部分
//router.js { path: 'userfriend', component: UserFriend, //添加好友信息 爲組件FriendInfo添加動態路由 children: [ //爲動態路由添加name //爲路徑參數添加Props數據下發 { path:'fd/:id',name:'fd',component:FriendInfo,props:true} ] }
爲組件FriendInfo
添加Props,並使用它。
<template> <!-- <div>好友{{$route.params.id}}信息</div> --> <div>好友{{id}}信息</div> </template> <script> export default { props: ['id'] } </script>
若是在命名路由中使用Props數據下發,要爲每個對應組件,都設置Props。
{ path: '/user/:id', components: { default: User, sidebar: Sidebar }, props: { default: true, sidebar: false } }
Props還能夠是一個對象
{ path:'fd/:id',name:'fd',component:FriendInfo,props:{id:1}}
還能夠是一個函數,函數接收一個$route
對象
function dynamicPropsFn (route) { const now = new Date() return { name: (now.getFullYear() + parseInt(route.params.years)) + '!' } } //... { path: '/dynamic/:years', component: Hello, props: dynamicPropsFn }
代碼 vue-router 0.4 鉤子、請求數據與全局進度條
咱們使用axios發送請求,http://schematic-ipsum.heroku... 能夠做爲響應模擬一些基本的數據。
改造好友信息組件FriendInfo.vue
,咱們在組件級鉤子beforeRouteUpdate
中發送請求。
<template> <!-- <div>好友{{$route.params.id}}信息</div> --> <div class="friend-info"> 好友{{id}}信息 <ul> <li v-if="name">姓名:{{name}}</li> <li v-if="phone">電話:{{phone}}</li> <li v-if="email">郵件:{{email}}</li> </ul> </div> </template> <script> import axios from 'axios' const reqObject = { "type": "object", "properties": { "name": { "type": "string", "ipsum": "name" }, "phone": { "type": "string", "format": "phone" }, "email": { "type": "string", "format": "email" } } } export default { props: ["id"], data(){ return { name:null, phone:null, email:null, } }, beforeRouteEnter(to, from, next) { console.log('beforeRouteEnter running..........') axios.post("http://schematic-ipsum.herokuapp.com/", reqObject) .then(response => { next(vm => { vm.name = response.data.name; vm.phone = response.data.phone; vm.email = response.data.email; }); }); } }; </script>
beforeRouteEnter
鉤子中第三個參數next()
,調用它時,才能夠繼續其餘操做(此時系統處於等待),咱們在獲取到響應時再調用它,所以在獲取響應後纔會看渲染效果。
仔細觀察能夠發現,屢次查看好友信息,信息不會改變,查看控制檯發現beforeRouteEnter
只調用了一次,只發送了一次信息。
首次查看信息時,解析路由並使組件FriendInfo
激活,咱們調用beforeRouteEnter
,以後每次查看,組件不會從新被渲染,只會被重複利用,所以不會再調用beforeRouteEnter
。注意該鉤子中不能使用this
,由於在執行它時組件尚未被實例,可是next()
的回調執行時,組件已被實例,它接收組件實例做爲參數(示例中的vm)
解決這個問題就靠beforeRouteUpdate
,該鉤子在當前路由改變,組件被複用時調用。好比從/interaction/userfriend/fd/1
到/interaction/userfriend/fd/2
時它就會調用。下面咱們就在FriendInfo
中添加這個鉤子。
beforeRouteUpdate(to, from, next) { axios.post("http://schematic-ipsum.herokuapp.com/", reqObject) .then(response => { this.name = response.data.name; this.phone = response.data.phone; this.email = response.data.email; next(); }); },
注意它是可使用this
的,它的next()
不接收回調。
咱們看下youtube
每次加載頁面會有個進度條
咱們如今就使用全局鉤子函數和NProgress實現這個功能,在入口app.js文件中添加全局路由鉤子。
import Vue from 'Vue' import nprogress from 'nprogress' import 'nprogress/nprogress.css' import router from './router' const app = function () { //在任何導航被觸發前執行 router.beforeEach((to, from, next) => { console.log('beforeEach') nprogress.start() next() }) //導航中的最後一個鉤子 router.afterEach((to, from) => { console.log('afterEach') nprogress.done() }) new Vue({ router, el: '#app' }) } export { app }
代碼 vue-router 0.5 爲<router-view>添加動畫
爲顯示好友信息添加動畫
修改組件UserFriend
<!-- UserFriend.vue --> <div> <transition name="slide-left" mode="out-in"> <router-view class="child-view"></router-view> </transition> </div>
添加過渡類
.child-view { position: absolute; transition: all 1s cubic-bezier(.55,0,.1,1); } .slide-left-enter { opacity: 0; -webkit-transform: translate(60px, 0); transform: translate(60px, 0); } .slide-left-leave-active { opacity: 0; -webkit-transform: translate(-60px, 0); transform: translate(-60px, 0); }
因爲在切換查看信息時,組件FriendInfo
不會從新渲染,即除第一次外不會有動畫,所以須要設置key
,這裏有個不可預期的錯誤,即在<router-view>
上設置key。
<!-- UserFriend.vue --> <router-view class="child-view" :key="$route.params.id"></router-view>
千萬不能這麼作,key
沒法傳遞給FriendInfo
的根節點。
咱們須要在組件FriendInfo
上設置key
//FriendInfo.vue <div class="friend-info" :key="id">
代碼 vue-router 0.6 重定向和別名
仿造個人好友
,構造關注的人
當點擊單數用戶時,顯示用戶信息,點擊雙數用戶時,顯示用戶被銷燬。
用戶信息爲一個組件,銷燬爲另外一個組件。
路由配置以下:
{ path: 'userFollow', component: UserFollow, children: [ { path:'fw/:id', name:'fw', redirect: to => { if(to.params.id%2===0) return 'fd/:id' else return 'fi/:id' }, beforeEnter:(to, from, next) => { console.log(from) console.log(to) next() } }, { path:'fd/:id', component:UserDestroy, props:true }, { path:'fi/:id', component:FollowInfo, props:true } ] }
每一個routerLink
的連接路徑爲fw/:id
,在redirect
中配置重定向函數,路徑參數id
爲單數重定向到路由fi/:id
,雙數時,重定向到fd/:id
。redirect
還能夠是個表示路由路徑的字符串或命名路由對象({name:'foo',params:{bar:'baz'}})。
另外在redirect
屬性所在的路由中,導航守衛不會執行,如上面的beforeEnter
不會執行(全局的守衛也不會執行)。
咱們增長一個頂級導航系統公告
<div class="nav-header"> <router-link to="/" exact>用戶首頁</router-link> <router-link to="/settings">用戶設置</router-link> <router-link to="/interaction">社交管理</router-link> <router-link to="/notification">系統公告</router-link> </div>
爲它添加路由
{ path: '/notification', component: SystemNotification }
SystemNotification
組件只是一個簡單的標題
<template> <div> <H3>系統公告</H3> </div> </template>
以後我想把這個頁面改爲個人消息頁面
爲此咱們修改路由路徑/usermessage
,將原來的名字配置爲別名alias:'/notification'
,並保持routerLink
的路徑不變。
這樣作一是其餘網站引用該頁面不會產生404
,二是路由內部配置引用該路由也不會找不到。
{ path: '/usermessage', component: SystemNotification, alias:'/notification' },
代碼 vue-router 0.7 滾動行爲
在個人消息
中配置路由及其視圖,以下圖:
配置4個路由及其對應組件
<H3>個人消息</H3> <router-link to="/notification/system-msg">系統消息</router-link> <router-link to="/notification/friend-msg">好友消息</router-link> <router-link to="/notification/group-msg">用戶組消息</router-link> <router-link to="/notification/other-msg">其餘消息</router-link> <router-view></router-view>
path: '/usermessage', component: SystemNotification, alias: '/notification', children: [ { path: 'system-msg', component: SystemMessage, meta: { scrollToTop: false } }, { path: 'friend-msg', component: FriendMessage, meta: { scrollToTop: true } }, { path: 'group-msg', component: GroupMessage, meta: { scrollToTop: true } }, { path: 'other-msg', component: OtherMessage, meta: { scrollToTop: true } } ]
四個組件中定義一系列字符串列表。在其中插入下一個消息列表的routerLink
,如
效果以下
使用歷史記錄回退時,保持原紀錄位置,是正常的。但點擊連接,跳到新頁面,也是原來位置,這不是咱們預期的行爲,咱們使用路由屬性scrollBehavior
解決這個問題。它與屬性routes
是同一級別的屬性。
//router.js scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition } else { const position = {} if(to.hash) { position.selector = to.hash if(to.hash === "#anchor") { position.offset = { y: 100 } } } if (to.matched.some(m => m.meta.scrollToTop)) { position.x = 0 position.y = 0 } return position; } }
爲了後面定位到錨點,咱們改造FriendMessage
,爲連接到下一個消息列表的鏈接添加hash#anchor
<li><router-link to="/notification/group-msg#anchor">用戶組消息</router-link></li>
修改GroupMessage
<ul> <li v-for="item in 29" :key="item">用戶組消息{{item}}</li> <li id="anchor" style="border:1px solid red;" key="30">用戶組消息30</li> <li v-for="item in 20" :key="item+30">用戶組消息{{item+30}}</li> <li><router-link to="/notification/system-msg">其餘消息</router-link></li> <li v-for="item in 30" :key="item+50">用戶組消息{{item+50}}</li> </ul>
scrollBehavior
接收3個參數to
、from
和savedPosition
。以上函數中savedPosition
當且僅當 popstate 導航 (經過瀏覽器的 前進/後退 按鈕觸發) 時可用,這裏與默認的效果沒區別。咱們獲取to
路由的元數據meta.scrollToTop
(保存自定義的數據),當爲true時咱們切換到to
對應的頁面時,咱們定位到{x:0,y:0}
,不然保持默認行爲。若是to
存在hash,設置{selector:to.hash}
定位到錨點,能夠具體定位錨點定位的偏移量{selector:to.hash,offset:{y:100}}
。
在以上全部的請求路徑都帶#
,這不是咱們所指望的,可是可用於全部的瀏覽器,這種模式爲默認的hash
模式,如:
http://localhost:8080/#/settings/useremail
如今使用history
模式,設置屬性mode
mode:"history", routes: [/*...*/], scrollBehavior(to, from, savedPosition) {/*...*/}
如今路徑就正常了,但它只能用於支持H5 History API的瀏覽器。
http://localhost:8080/settings/useremail
History API須要服務器的支持,不然當重載頁面時,會發生404頁面找不到,就像下面這樣。
這裏使用webpack-dev-server,設置webpack的devServer.historyApiFallback
爲true,使其支持History API。如
devServer: { historyApiFallback:true, noInfo: true },
其餘服務器的配置見官方文檔後端配置例子
而後又發現一個bug,像下面這樣,顯示http://localhost:8080/settings/app.js
找不到
其實這是個webpack的問題,插入js資源時,像下面這樣
它是相對於當前請求的路徑,爲了解決這個問題,咱們要在webpack中設置output
中的publicPath
屬性爲/
output: { path: path.resolve(__dirname, './dist'), publicPath: '/', filename: '[name].js', },
它在全部資源前加上虛擬路徑/
,app.js
就變爲絕對路徑localhost:8080/app.js
,這下就沒什麼問題了。