vue-router
是vue
官方的路由管理器,它和 Vue
的核心深度集成,让构建单页面应用变得很简单。
文章介绍的vue-router
只针对v3.x
版本。
在引入Vue
和vue-router
后使用Vue.use
方法安装路由功能。
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
如果是通过script
引入的文件,则不需要使用Vue.use
方法,它会自动安装。
<script src="/path/to/vue.js"></script>
<script src="/path/to/vue-router.js"></script>
只需要在vue
后面引入vue-router
即可,因为在vue-router
源码里已经执行了安装:
// vue-router/src/index.js
...
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
const Foo = { template: 'foo' }
const Bar = { template: 'bar' }
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
const router = new VueRouter({
routes
})
传入路由配置并初始化一个VueRouter
实例,将实例作为配置参数传递到Vue
实例中,即可让整个应用拥有路由功能。
new Vue({
router
})
vue-router
提供了router-link
和router-view
两个组件来实现路由的跳转及视图的切换。
组件支持用户在具有路由功能的应用中 (点击) 导航。通过 to
属性指定目标地址,默认渲染成带有正确链接的 标签。
<router-link to="foo">to foorouter-link>
<router-link to="bar">to barrouter-link>
组件是一个 functional
组件,渲染路径匹配到的视图组件。
在创建Vue
的时候传入template
new Vue({
el: '#app',
router,
template: "to foo to bar "
})
在配置路由时使用“动态路径参数”,来实现多个路径全部映射在同一个组件上。
{path: '/foo/:id', component: Foo}
一个“路径参数”使用冒号 :
标记。当匹配到一个路由时,参数值会被设置到 this.$route.params
中,这样即便传递的参数不同,都是通过渲染同一个组件,在组件中通过this.$route.params
来获取不同的数据进行展示。
比如上面的例子在地址栏输入xxx/foo/1
,可以通过this.$route.params.id
获取到值为"1"
。
如果输入的地址没有携带id
,比如xxx/foo
,没有命中设置的path
,就不会渲染组件。
多段路径参数
也可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params
中
{path: '/foo/:id/post/:postId', component: Foo}
此时如果输入的地址为xxx/foo/1/post/2
,那么this.$route.params
的值为{id: "1", postId: "2"}
存在问题,当存在动态参数时候点击router-link跳转到其他页面,路径会叠加而不是重置
当只改变路由参数时,比如从/foo/1
导航到/foo/2
,原来的组件会复用,不会进行进行重新渲染,也就意味着组件的生命周期钩子不会在被调用。
在这种情况下,可以直接通过watch
来对this.$route
进行监听,实现数据的更新。
watch: {
$route(to, from) {
// ...
}
}
还有一种方式是通过路由守卫来实现,在引入了vue-router
后,给Vue
添加了几个方法。
beforeRouteEnter
、beforeRouteLeave
、beforeRouteUpdate
const Foo = {
template: `foo{{$route.params.id}}`,
methods: {
updateId() {
this.$router.push('/foo/'+Math.random());
}
},
beforeRouteUpdate(to, from, next) {
// 必须调用next方法,不然不会更新路由
next();
}
}
前面提到可以通过$route.params
来获取传递给组件的值,但是这样会使得与对应组件高度耦合,让组件只有某些特定的url
上使用。
比如:
const Foo = { template: `foo{{$route.params.id}}`}
可以使用props
来解耦
const Foo = {
props: ['id'],
template: `foo{{id}}`
}
const routes = [
{
path: '/foo/:id',
component: Foo,
props: true
}
]
在routes
上配置props
为true
,route.params
将会被设置为组件属性。
当然可以设置一个对象。
const routes = [
{
path: '/foo/:id',
component: Foo,
props: { fromUrl: true }
}
]
对于动态的值,可以传递一个方法
const getDynamicValue = () => {
return Math.random();
}
const routes = [
{
path: '/foo/:id',
component: Foo,
props: { dynamicValue: getDynamicValue }
}
]
*
通配符常规参数只会匹配被 /
分隔的 URL
片段中的字符。如果想匹配任意路径,我们可以使用通配符 *
。
{
// 会匹配所有路径
path: '*'
}
{
// 会匹配以 `/foo-` 开头的任意路径
path: '/foo-*'
}
如果设置了通配符,一定要确保路由的顺序,把设置通配符的路由放在最后,不然所有的路由的都会被通配符捕获从而导致渲染异常。路由 { path: '*' }
通常用于客户端 404
错误。
当使用一个通配符时,$route.params
内会自动添加一个名为 pathMatch
参数。它包含了 URL
通过通配符被匹配的部分:
// 给出一个路由 { path: '/foo-*' }
this.$router.push('/foo-1')
this.$route.params.pathMatch // '1'
// 给出一个路由 { path: '*' }
this.$router.push('/foobar')
this.$route.params.pathMatch // '/foobar'
源码里的matchRoute
方法里添加了pathMatch
属性
function matchRoute (
regex: RouteRegExp,
path: string,
params: Object
): boolean {
const m = path.match(regex)
// ...
for (let i = 1, len = m.length; i < len; ++i) {
const key = regex.keys[i - 1]
if (key) {
params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i]
}
}
}
由于vue-router
使用 path-to-regexp
作为路径匹配引擎,因此支持很多高级的匹配模式。例如:可选的动态路径参数、匹配零个或多个、一个或多个,甚至是自定义正则匹配。更多可以查看path-to-regexp
在定义的路由的时候添加 name
属性
const routes = [
{ path: '/foo/:id', component: Foo, name:'fooName' },
{ path: '/bar', component: Bar, name: 'barName' }
]
给to
属性传递一个对象,不能跟传递路径一样只传一个string。
<router-link :to="{name: 'fooName', params: {id: 1}}">to foorouter-link>
上述的跳转的跟使用router
的方法一样。
router.push({name: 'fooName', params: {id: 1}})
对于一些多层嵌套的组件,可以使用嵌套路由来表示。
在routes
配置中提供了一个children
字段来实现嵌套。
const Foo = { template: `Foo view
` }
const Bar = { template: `Bar view` }
const Profile = { template: `Profile view` }
const routes = [
{
path: '/foo/:id',
component: Foo,
children: [
{
// 当路径满足 /foo/:id/bar时就会在Foo组件的router-view中渲染Bar组件
path: 'bar',
component: Bar
// 同样可以配置childrens,可以实现多层桥套
},
{
// 当路径满足 /foo/:id/bar时就会在Foo组件的router-view中渲染Bar组件
path: 'profile',
component: Profile
}
]
}
]
注意,在上述配置中,如果访问/foo/:id
路径,因为没有匹配到子路由,所以组件内的router-view
不会渲染任何东西。如果想默认渲染,可以提供一个空路由。
const routes = [
{
path: '/foo/:id',
component: Foo,
children: [
// ...
{
path: '',
component: { template: `default view` }
}
]
}
]
只能用''
而不能用*
去匹配。
直接在routes
中添加一个rediect
字段,这样所有访问/foo
的页面都会重定向到/bar
{ path: '/bar', name: 'bar', component: Bar},
{ path: '/foo', redirect: '/bar' }
也可以是一个命名路由对象
{ path: '/foo', redirect: { name: 'bar' } }
也可以是一个函数
{ path: '/foo', redirect: (to) => {
// 只接收了一个目标路由作为参数
// 需要return 出可以重定向的路由路径/对象,否则不渲染组件
} }
非嵌套,同级展示视图,可以使用命名视图。
<router-view class="header">router-view>
<router-view class="content" name="content">router-view>
<router-view class="bottom" name="bottom">router-view>
在路由配置中添加多个组件,
const Header = { template: 'Header
'}
const Content = { template: 'Content
'}
const Bottom = { template: 'Bottom
'}
const routes = [
{ path: '/',
components: {
default: Header,
content: Content,
bottom: Bottom
}
},
]
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。
有多种方式可以植入路由导航:
使用router.beforeEach
方法注册全局前置守卫,所有路由在跳转前都会执行这个方法。
const router = new VueRouter({...})
router.beforeEach((to, from, next) => {
next();
})
beforeEach
接收三个参数:
to
: 要跳转的目标路由对象from
:要离开的当前路由对象next
:一定要调用该方法来 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)
: 如果传入 next
的参数是一个 Error
实例,则导航会被终止且该错误会被传递给 router.onError()
注册过的回调。用 router.beforeResolve
注册一个全局守卫。这和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
在路由跳转完后执行,这个与守卫不同的是不接收next
参数,因为它并不会影响导航本身。
router.afterEach((to, from) => {
// ...
})
在routes
配置上直接定义beforeEnter
守卫(路由独享守卫只有beforeEnter
一个)。
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
直接在组件上定义就行,有以下三种:
beforeRouteEnter
: 在渲染该组件的对应路由被 confirm
前调用,此时组件实例还没有被创建,因此不能使用this
。beforeRouteUpdate
:在当前路由改变,但是该组件被复用时调用。比如对于带有动态参数的路径来说,/foo/:id
,只改变id
,组件实例被复用就会执行这个钩子。beforeRouteLeave
:导航离开该组件的对应路由时调用。虽然beforeRouteEnter
在组件实例未被创建之前执行,不能访问this
。但是可以通过给 next
来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
beforeRouteLeave
守卫。beforeEach
守卫。beforeRouteUpdate
守卫。beforeEnter
。beforeRouteEnter
。beforeResolve
守卫。afterEach
钩子。DOM
更新。beforeRouteEnter
守卫中传给 next
的回调函数,创建好的组件实例会作为回调函数的参数传入。