继上一片博客,在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。使用 props
将组件和路由解耦,这样你便可以在任何地方使用该组件,使得该组件更易于重用和测试。
{
path: '/page1/:param',
name: 'page1',
component: () => import('../views/page1.vue'),
// 增加props属性
props: true
},
<template>
<div>{{ param }}</div>
</template>
<script>
export default {
// 接收param参数
props: ['param']
}
</script>
访问http://localhost:8080/#/page1/qwe
{
path: '/page4',
component: () => import('../views/page4.vue'),
props: {
param: 123
}
},
<template>
<div>{{ param }}</div>
</template>
<script>
export default {
props: ['param']
}
</script>
访问http://localhost:8080/#/page4
{
path: '/page4',
component: () => import('../views/page4.vue'),
props: route => ({
param: route.query.param
})
},
page4.vue
组件保持不变。访问http://localhost:8080/#/page4?param=qwe
src/router/index.js
...
const router = new VueRouter({
// 设置mode属性
mode: 'history',
routes
})
...
vue-router
默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
但是这样一来,URL变多出来一个很丑多/#
。如果不想要很丑的 hash
,我们可以用路由的 history
模式,这种模式充分利用 history.pushState API
来完成 URL 跳转而无须重新加载页面。当你使用 history
模式时,URL 就像正常的 url,例如 http://localhost:8080/page4
,也好看!
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://localhost:8080/aaaaa
就会返回 404,这就不好看了。
所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个统一的页面,这个页面可以为我们的默认/
所对应的App.vue
页面,或者是我们自己所定义的404页面,当设置成返回我们自己定义的404页面时,我们的路由列表的最后要加上
{
path: '*',
component: () => import('../views/404.vue')
}
这里注意一定要加在最后,因为路由列表的优先级为从上到下。后端配置例子请移步官网https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
导航守卫能够帮我们在路由发生跳转到导航结束这段时间内做一些逻辑处理。如跳转某页面时判断用户有没有登录,如果没有登录,则跳转到登录页,如果已经登录则正常访问。
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
src/router/index.js
...
const router = new VueRouter({
routes
})
router.beforeEach((to, from, 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()
注册过的回调。
**确保 next
函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。
示例:
...
// 用户登录状态
const LOGIN_IN = true;
router.beforeEach((to, from, next) => {
// 如果访问到页面不是home页
if (to.name != 'Home') {
// 如果已经登录 跳转对应页面
if (LOGIN_IN) next();
// 若果没登录,跳转home页
else next({ name: 'Home' })
} else { // 如果访问到是home页
// 如果应登录 跳转到page2页面
if (LOGIN_IN) next('/page2');
如果没登录 跳转到home页
next();
}
})
...
router.afterEach((to, from) => {
// 路由跳转之后做一些操作
})
导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
router.beforeEach((to, from, next) => {
// ...
})
{
path: '/',
name: 'Home',
component: Home,
alias: '/home',
beforeEnter: (to, from, next) => {
if (from.name == 'About') console.log('这是从about页来到');
else console.log('这是从其他页来的');
next();
}
},
实验一个守卫实例时,建议注释之前的守卫,这样更加清晰且不受干扰。
home.vue
组件
...
export default {
name: 'Home',
components: {
HelloWorld
},
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 (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫。对于 beforeRouteUpdate
和 beforeRouteLeave
来说,this
已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteUpdate (to, from, next) {
this.name = to.params.name
next()
}
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false)
来取消。
beforeRouteLeave (to, from, next) {
const answer = window.confirm('你真的想离开吗?您有未保存的更改!')
if (answer) {
next()
} else {
next(false)
}
}
beforeRouteLeave
守卫。beforeEach
守卫。beforeRouteUpdate
守卫 (2.2+)。beforeEnter
。beforeRouteEnter
。beforeResolve
守卫 (2.5+)。afterEach
钩子。DOM
更新。beforeRouteEnter
守卫中传给 next
的回调函数。路由列表里的每个路由对象可以配置一个meta
字段,里面存放一些我们定义的信息,比如,依靠模板打包出来的项目每个页面的title
都是一样的,现在我们需要每跳转一个页面,更改该页面的title
。
...
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue'),
meta: {
title: '关于'
}
},
...
在beforeEach
守卫中访问meta字段
...
router.beforeEach((to, from, next) => {
if (to.meta && to.meta.title) {
window.document.title = to.meta.title;
}
next();
});
...
页面的切花那就是两个组件,一个组件注销一个组件加载。
为多个组件设置
<transition-group name="router">
<!-- transition-group里的视图需要设置不同的key -->
<router-view key="default" />
<router-view key="main" name="main" />
</transition-group>
单个组件
<transition name="router">
<router-view/>
</transition>
使用css设置效果
...
<style lang="less">
...
// router为transition或transition-group的name值
.router-enter {
opacity: 0;
}
.router-enter-active {
transition: opacity 1s ease;
}
.router-enter-to {
opacity: 1;
}
.router-leave {
opacity: 1;
}
.router-leave-active {
transition: opacity 1s ease;
}
.router-leave-to {
opacity: 0;
}
...
</style>
name
属性可以变成一个动态的值
...
<transition :name="routerName">
<router-view/>
</transition>
...
<script>
export default {
data () {
return {
routerName: ''
}
},
watch: {
// 监听当前路由对象
'$route' (to) {
if (to.query && to.query.routerName) {
this.routerName = to.query.routerName
}
}
}
}
</script>
...
当访问/name_pag
时并无过渡,访问/name_page?routerName=router
出现过渡效果。这样方式可以为特定页面设置特定过渡。