导航守卫的作用:
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。》》项目中应用场景举例:路由页面跳转时候进行登陆验证;路由跳转判断;
有多种机会植入路由导航过程中:全局路由, 单个路由独享的, 或者组件级的。
全局守卫包括:router.beforeEach(是全局前置守卫)、router.beforeResolve(是全局解析守卫)、router.afterEach(是全局后置钩子)
单个路由独享的导航守卫:在路由配置上直接定义
beforeEnter
守卫组件内守卫包括:在组件内定义路由的导航守卫,有beforeRouteEnter 、
beforeRouteUpdate 、
beforeRouteLeave
每个导航守卫都接受3个参数 from 、to、next() ,参数的定义如下:
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那 么 URL 地址会重置到 from 路由对应的地址。
next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
注意事项:
1、确保要调用
next
方法,否则钩子就不会被 resolved。2、当一个导航触发时,导航守卫按照顺序调用。守卫是异步解析执行,导航在所有守卫 resolve 完之前一直处于 等待中。
首先vue-router知识回顾:
0、在vue项目中使用vue-router步骤概述:
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: 'foo' }
const Bar = { template: 'bar' }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们暂时不套路讨论嵌套路由、传参、元数据、导航守卫等。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')
// 现在,应用已经启动了!
1、可以在任何组件内通过 this.$router
访问路由器,也可以通过 this.$route
访问当前路由:
// Home.vue
export default {
computed: {
username () {
// 我们很快就会看到 `params` 是什么
return this.$route.params.username
}
},
methods: {
goBack () {
window.history.length > 1
? this.$router.go(-1)
: this.$router.push('/')
}
}
}
2、动态路由匹配时使用this.$route.params获取路由的路径参数
应用场景:需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User
组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。又像 /user/foo
和 /user/bar
都将映射到相同的路由。
注意:可以在一个路由中设置多段“路径参数”,如下:
模式 匹配路径 $route.params /user/:username /user/evan { username: 'evan' }
/user/:username/post/:post_id /user/evan/post/123 { username: 'evan', post_id: '123' }
当使用路由参数时,例如从
/user/foo
导航到/user/bar
,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。参数或查询的改变并不会触发进入/离开的导航守卫。可以通过观察
$route
对象来应对这些变化,或使用beforeRouteUpdate
的组件内守卫。
3、复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route
对象:
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}
或者使用 2.2 中引入的 beforeRouteUpdate
导航守卫:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
4、在router配置文件中使用通配符 (*
)捕获所有路由或 404 Not found 路由
注意:通常含有通配符的路由应该放在最后。路由
{ path: '*' }
通常用于客户端 404 错误。如果你使用了History 模式,请确保正确配置你的服务器。同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。
当使用一个通配符时,$route.params
内会自动添加一个名为 pathMatch
参数。它包含了 URL 通过通配符被匹配的部分:
// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
// 给出一个路由 { path: '*' }
this.$router.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'
5、使用vue-router嵌套路由时,在路由定义文件中使用children
配置:
注意事项:以
/
开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 中
path: 'posts',
component: UserPosts
}
]
}
]
})
6、vue-router路由导航可以在组件中使用
的形式外,还有JS编程式的导航(包括可以调用 this.$router.push
(location, onComplete?, onAbort?)
和
router.replace(location, onComplete?, onAbort?),以及router.go(n)
)
注意事项:
- 如果提供了
path
,params
会被忽略;- 如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个
/users/1
->/users/2
),你需要使用beforeRouteUpdate
来响应这个变化 (比如抓取用户信息);router.push
方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。router.replace
方法它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。- 在 2.2.0+,可选的在
router.push
或router.replace
中提供onComplete
和onAbort
回调作为第二个和第三个参数。这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用。
const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
router.push
、 router.replace
和 router.go
跟 window.history.pushState
、 window.history.replaceState
和 window.history.go
好像, 实际上它们确实是效仿 window.history
API 的。
7、当需要在一个组件页面展示多个路由视图 router-view ,需要给视图命名:
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
注意:正确使用
components
配置 (带上 s):
8、vue-router路由重定向和别名使用
“重定向”的意思是,当用户访问 /a
时,URL 将会被替换成 /b
,然后匹配路由为 /b
;重定向不能指向自身,会形成死循环。
“别名”的意思是给路由取“小名”,/a
的别名是 /b
,意味着,当用户访问 /b
时,URL 会保持为 /b
,但是路由匹配则为 /a
,就像用户访问 /a
一样。
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' },
{ path: '/b', redirect: { name: 'foo' }},
{ path: '/c', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}},
{ path: '/a', component: A, alias: '/b' }
]
})
9、vue-router使用路由进行组件传参时,通过props取代 $route.params 可以降低耦合度:
注意:在组件中使用
$route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。通过props取代,这样你便可以在任何地方使用该组件,使得该组件更易于重用和测试。如果
props
被设置为true
,route.params
将会被设置为组件属性。
const User = { //$route 的耦合
template: 'User {
{ $route.params.id }}'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
const User = { //通过 props 解耦
props: ['id'],
template: 'User {
{ id }}'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
如果 props
是一个对象,它会被按原样设置为组件属性。当 props
是静态的时候有用。
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }
]
})
你可以创建一个函数返回 props
。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。
const router = new VueRouter({
routes: [
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
]
})
开始导航守卫使用demo:
注意事项:注意导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上。
1、全局前置守卫 router.beforeEach
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
2、全局解析守卫 router.beforeResolve
在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
3、全局后置钩子router.afterEach
和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
router.afterEach((to, from) => {
// ...
})
4、路由独享的守卫router.beforeEnter
是在路由配置上直接定义 beforeEnter
守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
5、组件内的守卫 beforeRouteEnter、beforeRouteUpdate
(2.2 新增)、beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
// 一般来控制弹框和跳转。
}
}
关于beforeRouteEnter
守卫 不能 访问 this
的解决方式是:通过传一个回调给 next
来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
注意:
beforeRouteEnter
是支持给next
传递回调的唯一守卫。对于beforeRouteUpdate
和beforeRouteLeave
来说,this
已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 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)
}
}
beforeEach
守卫。beforeRouteUpdate
守卫 (2.2+)。beforeEnter
。beforeRouteEnter
。beforeResolve
守卫 (2.5+)。afterEach
钩子。beforeRouteEnter
守卫中传给 next
的回调函数。
路由元信息:
定义路由的时候可以设置meta字段,可以存储该路由相关信息(例如:设置每个路由的title,取路由的title设置为选项卡的标题);还可以用来设置页面权限要求
{
path: '/router2',
name: 'router2',
component:router2,
meta:{
title:"router2"
}
}
// 全局前置守卫
router.beforeEach((to,from,next) => {
console.log(to);
console.log(from);
if(to.meta.title) {
document.title = to.meta.title;
} else {
document.title = '我是默认的title'
}
next();
});
路由过渡动效:
Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。
const Foo = {
template: `
...
`
}
const Bar = {
template: `
`
}
// 接着在父组件内
// watch $route 决定使用哪种过渡
watch: {
'$route' (to, from) {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}
路由数据获取:(分别有导航完成前和完成后获取)
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。
导航完成后获取数据,我们会马上导航和渲染组件,然后在组件的 created
钩子中获取数据。
export default {
data () {
return {
loading: false,
post: null,
error: null
}
},
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},
methods: {
fetchData () {
this.error = this.post = null
this.loading = true
// replace getPost with your data fetching util / API wrapper
getPost(this.$route.params.id, (err, post) => {
this.loading = false
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
}
}
我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter
守卫中获取数据,当数据获取成功后只调用 next
方法。
export default {
data () {
return {
post: null,
error: null
}
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
beforeRouteUpdate (to, from, next) {
this.post = null
getPost(to.params.id, (err, post) => {
this.setData(err, post)
next()
})
},
methods: {
setData (err, post) {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
}
}
}
路由滚动行为:
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。
vue-router
能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。注意: 这个功能只在支持
history.pushState
的浏览器中可用。
当创建一个 Router 实例,你可以提供一个 scrollBehavior
方法:
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})
scrollBehavior
方法接收 to
和 from
路由对象。第三个参数 savedPosition
当且仅当 popstate
导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
这个方法返回滚动位置的对象信息,长这样:
{ x: number, y: number }
{ selector: string, offset? : { x: number, y: number }}
(offset 只在 2.6.0+ 支持)如果返回一个 falsy (译者注:falsy 不是 false
,参考这里)的值,或者是一个空对象,那么不会发生滚动。
举例:
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
对于所有路由导航,简单地让页面滚动到顶部。
返回 savedPosition
,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
如果你要模拟“滚动到锚点”的行为:
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash
}
}
}
路由懒加载:
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
一个能够被 Webpack 自动代码分割的异步组件。
const Foo = () => import('./Foo.vue')
在路由配置中什么都不需要改变,只需要像往常一样使用 Foo
:
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
vue路由登陆验证:
前言:vue的项目的登录状态如果用vuex状态管理,页面一刷新vuex管理的状态就会消失,所以,会将登录的状态写到web Storage中进行存储管理。
步骤:
1、router/index.js 》在需要登录验证的路由元信息里加入登录验证标识requiresAuth:true
routers: [
{ path: '/home',
name: 'home',
component: Home,
meta: { requiresAuth: true }
},
{
path: '/login',
name: 'Login',
component: Login
},
]
2、登陆组件从服务端获取到token,将登录状态写入web Storage里
login() {
this.$api.login().then(function(res) {//调用axios二次封装的login方法,关闭mockjs时会传递到nodejs
sessionStorage.setItem('accessToken', res.data.token) // 成果返回后放置token到Cookie
//console.log("登陆响应",JSON.stringify(res.data));
router.push('/home') // 登录成功,跳转到主页
}).catch(function(res) {
alert(res);
});
},
3、router/index.js 》再路由配置文件中使用全局前置导航守卫
//全局前置导航守卫,导航跳转前进行token登陆令牌判断
router.beforeEach((to, from, next) => {
if(to.path === '/login') {
next()
}else {
if(to.meta.requiresAuth && !sessionStorage.getItem('accessToken')) {
next({ path: '/login' })
}else { //如果不需要登录验证,或者已经登录成功,则直接放行
next()
}
}
});
官网API:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html