前言
Vue Router
啥用呢,其实就是前端来更改页面路径的一个玩意,一般来说,网页的跳转都是要后台来控制的,现在有了它,前端自己就可以定义路由。当然,后台得提供一个默认渲染页面index.html
,所以有了这玩意,做个单页面应用就很方便。
正文
实现原理
Vue
的思想就是一切皆组件
,那么开发单页面应用的时候,它在加载页面的时候不会加载整个页面,而只加载指定的组件,那么当页面路径的改变的时候,并不是去更新页面,而是更新里面的组件。它的实现有两种模式:Hash模式
和History模式
,这个下面再讲。
基本例子
HTML
Go to Foo
//name 为路由的名称,params为参数
//留坑,加载路径下默认组件
JavaScript
import Foo from "@/components/Foo"
const routes = [
{
path: '/foo',
component: Foo
}
]
export default new VueRouter({
routes
})
全局注册
//main.js
import router from "./router"
new Vue({
el: '#app',
router,
components: { App },
template: ' '
})
动态路由匹配
模式 | 路径 | $route.params |
---|---|---|
/user/:username | /user/evan | { username: 'evan' } |
/user/:username/post/:post_id | /user/evan/post/123 | { username: 'evan', post_id: '123' } |
在组件中访问路由的参数方法:
this.$router.params
,此外还有this.$router.query
(获取查询参数),this.$router.hash
(获取hash值)
嵌套路由
是最顶层的出口,当它匹配到的组件中还包含
那么就要用到嵌套路由,children
就要出场了。
routes : [
{
path : '/user/:id',
component : User,
children : [
{
path : 'profile',
component : UserProfile
}
]
}
]
编程式的导航
啥意思呢,上面的内容是讲怎么在html里面放一个a标签,下面要讲的是,怎么用js实现链接跳转。
router.push()
this.$router.push({name: 'user',params: {userId: 123}})
// 也可以简写一下
// 这个也适用于router-link的to属性
const userId = 123
this.$router.push({path: `user/${userId}` })
router.replace()
跟router.push()
类似,区别就是,router.replace()
不会向History
添加新纪录,意思就是说不能通过go(-1)
返回上一个路由
router.go()
跟原生JS的window.history.go()
一个意思
命名视图
加载的是路由下默认的组件,当一个路由下有好多个组件的时候,就需要给
加一个name
属性,让它对应相应的组件。
import Bar form '@/component/Bar'
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar
}
}
]
重定向
routes: [
{
path: '/a',
redirect: '/b' //重定向到/b
},
{
path: '/a',
redirect: {
name: 'foo' //重定向到一个命名的路由
}
},
{
path: '/a',
redirect: to => {
const { hash, params, query} = to;
//根据hash query.to params.id 判断
//最后 return 一个路径
}
}
]
别名
如果要实现路径不变,但是访问的是另外一个路由的东西,那么就要用到别名alias
{path: '/a',alias: '/b'}
/b
是/a
的别名,当访问/b
或者/a
时候,路径不会改变,但是访问的视图都是/a
路由下的视图
高级一点用法就是嵌套路由
{
path: '/home',
children: [
{
{
path: 'foo',
alias: ['/foo',foolis]
}
}
]
}
上面的代码中,当输入路径为/foo
或者home/foolis
或者home/foo
渲染的都是相同的视图
路由传参
前面提到可以通过parms={}
传递参数,在组件中获取参数
{{ $route.params.id }}
这种方式会形成高度耦合,更高明的方式就是用props
{path: '/user/:id',props: true}
当props
设置为true
时,route.params
将会变成组件属性,组件中就可以直接使用{{ id }}
获取参数id
{path: '/user/:id',props: {name: 'world'}}
传给组件一个对象
props
也可以用函数模式(获取查询参数的id)
const router = new VueRouter({
routes: [
{path: '/user/:id',props: (route) => ({ query: route.query.id })}
]
})
导航守卫
啥是导航守卫呢,就好比假如每一个url
路径代表一扇门,那么就有很多个门,有一个人穿过这些门,每两个门中间的路上有那么一个摄像头,那么摄像头就是导航守卫
,它就能知道这个人从哪扇门过来的,准备去哪扇门。
参数和查询改变并不需要去另外一扇门,所以摄像头就看不到,遇到这种情况就需要用到
beforeRouteUpdate
全局守卫
const router = new VueRouter({})
router.beforeEach((to, from, next) => {
// 不管函数体内作何判断,最终都必须调用next()
})
上面注册了一个全局前置守卫,一旦有一个导航被出发的时候,它就会调用。next()
可以传递参数,当传递一个路由的时候就会中断当前的导航转向去另外一个导航,当参数为false
时,返回from
的路由。
与全局前置守卫对应的还有一个后置守卫
afterEach()
,但是后置守卫是没有next
参数的
路由独享的守卫
可以直接在路由配置上定义一个beforeEnter
守卫
routes: [
{
path: '/foo',
beforeEnter((to,from,next) => {
})
}
]
组件内的守卫
export default{
data() {
return {}
},
beforeRouteEnter(to, from, next) {
//渲染组件前调用
//不!能!使用this,因为实例还没创建呢
},
beforeRouteUpdate(to, from, next) {
// 路由更变但是视图没咋变的时候调用
// 比如参数或者查询内容的变化
// 可以使用this,因为组件被复用了
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以使用this
// 主要用来防止用户未保存就离开页面
}
}
beforeRouteEnter
虽然不能访问this
,但是可以传一个回调next
来访问实例
beforeRouteUpdate(to, from, next) {
next(vm => {
// 通过vm访问组件实例
})
}
beforeRouteEnter
是唯一一个可以回调的守卫
完整的导航流程
- 全局的
beforeEach
守卫 - 重用组件中的
beforeRouteUpdate
守卫 - 路由配置里面的
beforeEnter
守卫 - 解析异步路由组件
- 在被激活的组件里调用
beforeEnter
- 调用全局的
beforeResolve
守卫 - 导航被确认
- 调用全局的
afterEach
路由元信息
{
path: '/foo',
meta: {
requireIsLogin: true
}
}
meta
字段就是元信息,它的值可以随便定义,它有啥用,主要是给页面加了一个验证,假如有一些页面需要登录权限,那么就可以根据这个元字段来标识这些页面。下面用代码来实现
router.beforeEach((to, from, next) => {
if(to.match.some(record => record.meta.requireIsLogin)) {
if(!isLogin) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
}else{
next()
}
}else{
next() // 一定要调用next()
}
})
滚动行为
使用前端路由,当切换到新路由后,页面默认保持的的是原来的滚动位置,如果想让它滚动到顶部就像重新加载的一样,就需要用到scrollBehavior
const router = new VueRouter({
routes:[],
scrollBehavior(to, from, savedPosition) {
if(savedPosition) {
return savedPosition
}else{
return {x: 0, y: 0}
}
}
})
第三个参数savedPosition
仅且仅当是通过浏览器的前进/后退
按钮触发的才可用。
如果返回一个
falsy
(不是false
),或者一个空对象,那么不会发生滚动。
如果要模拟滚动到锚点
scrollBehavior(to, from, savedPosition) {
if(to.hash) {
return {
selector: to.hash
}
}
}
当然也可以延迟滚动,用Promise
来实现
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({x: 0, y: 0})
},500)
})
}