代码
官方给出下面的例子在不使用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
,这下就没什么问题了。