简单来说:Vue-Router本质是将url和组件建立映射关系
路由: <router-link :to="path">
动态路由
我们每个人都有自己的主页,显示主页内容用的都是同一个组件,这时只需要更改传进来显示的用户的id就能显示对应的用户内容,而并不需要重新写一个组件。
例如:juejin.cn/user/449027…;更改后面的id就是不同的用户
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User },
// 多段路径
{ path: '/city/:cityname/house/:house_id', }
]
})
“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const City = {
template: '<div>City {{ $route.params.cityname }} -- House {{house_id}}</div>'
}
路由变化
当使用路由参数时,原来的组件实例会被复用,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。
但是会触发路由的导航守卫: beforeRouteUpdate
两种检测路由对象的变化方法:
const User = {
template: '...',
watch: {
$route(to, from) {
// from 从何路由 跳向 to目的路由
// 对路由变化作出响应...
}
}
}
const User = {
template: '...',
beforeRouteUpdate(to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
捕获未指定路由
常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配非指定的任意路径,我们可以使用通配符 (*):
{
// 会匹配所有路径
path: '*'
}
{
// 会匹配以 `/user-` 开头的任意路径
path: '/user-*'
}
当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' } 通常用于客户端 404 错误。
$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'
嵌套路由
URL 中各段动态路径也按某种结构对应嵌套的各层组件,借助 vue-router,使用嵌套路由配置,就可以很简单地表达这种关系。
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
组件嵌套
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
要注意,以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。
Vue文件的根文件就是以 / 开头的嵌套路径
<div id="app">
<router-view/>
</div>
命名路由
你可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
要链接到一个命名路由,可以给 router-link 的 to 属性传一个对象:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
命名视图
你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="helper"></router-view>
然后你可以用这个路由配置完成该布局:
{
path: '/settings',
// 你也可以在顶级路由就配置命名视图
component: UserSettings,
children: [{
path: 'emails',
component: UserEmailsSubscriptions
}, {
path: 'profile',
components: {
default: UserProfile,
helper: UserProfilePreview,
a: Loading
}
}]
}
路由重定向和别名
重定向
重定向也是通过 routes 配置来完成,下面例子:
const router = new VueRouter({
routes: [
// 是从 `/a` 重定向到 `/b`
{ path: '/a', redirect: '/b' },
// 重定向的目标也可以是一个命名的路由
{ path: '/c', redirect: { name: 'foo' }},
// 甚至是一个方法,动态返回重定向目标
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})
注意:重定向不会触发导航守卫
别名
正如人的小名一样,虽然叫的名字不同,但是你还是你;路由也是一样,路径名不同但是还是会跳到一样的组件
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
编程式路由
除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。
注意:在 Vue 实例内部,你可以通过 $router 访问路由实例。
router对象方法
$router.push()
想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
点击 <router-link :to="..."> 等同于调用 router.push(...)。
router.push(location, onComplete?, onAbort?)
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
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
$router.replace()
跟 router.push 很像,但是它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
用法是一样的
$router.go()
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
History原生方法
如果你已经熟悉 Browser History APIs (opens new window),那么在 Vue Router 中操作 history 就是超级简单的。
还有值得提及的,Vue Router 的导航方法 (push、 replace、 go) 在各类路由模式 (history、 hash 和 abstract) 下表现一致。
路由传参
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [{ path: '/user/:id', component: User }]
})
在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
通过 props 解耦
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
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 被设置为 true,route.params 将会被设置为组件属性。
对象值
如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用。
函数模式
你可以创建一个函数返回 props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。
const router = new VueRouter({
routes: [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
})
URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件。
路由元信息
定义路由的时候可以配置 meta 对象:
routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录。
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})
一个路由匹配到的所有路由记录会暴露为 $route 对象;
示例:遍历路由匹配字段数组,查看记录中meta字段确定是否要登录
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // 确保一定要调用 next()
}
})
过渡效果
<router-view> 是基本的动态组件,跳转不同路由类似使用不同组件
<!-- 使用动态的 transition name -->
<transition :name="transitionName">
<router-view></router-view>
</transition>
// 接着在父组件内
// 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'
}
}
导航守卫
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
记住参数或查询的改变并不会触发进入/离开的导航守卫。
可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。
全局守卫
全局前置守卫
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
示例:常用作于用户身份验证
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
每个守卫方法接收三个参数:
-
to: 即将要进入的目标 路由对象 -
from: 当前导航正要离开的路由对象 -
next: 执行下一步的方法,一定要调用该方法来resolve这个钩子;这个函数可以传参数:- next()/next(true) : 进入管道的下一个钩子;执行完毕导航状态就是 comfirmed
- next(false) :中断当前路由;用户可以手动点浏览器后退按钮回到from的地址
- next('/') / next({path: '/'}) :跳到一个指定的地址;next方法可以和$router.push() 传一样的参数
- next(error) :终止导航,错误传给
router.onError()
全局解析守卫
用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局后置钩子
不会接受 next 函数也不会改变导航本身
router.afterEach((to, from) => {
// ...
})
路由独享守卫
在路由配置上直接定义 beforeEnter 守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫
在路由组件内直接定义以下路由导航守卫:
●beforeRouteEnter:组件进入之前,组件实例还没被创建(next(vm => {})) ●beforeRouteUpdate:组件更新 ●beforeRouteLeave:组件离开之前
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
next(vm => {
// 通过 `vm` 访问组件实例
})
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
数据请求
进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
导航完成后获取数据
在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},
在导航完成前获取数据
beforeRouteEnter (to, from, next) {
getPost(to.params.id, () => {
next(vm => vm.fetchData())
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
beforeRouteUpdate (to, from, next) {
this.post = null
getPost(to.params.id, () => {
this.fetchData()
next()
})
},
滚动行为
当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState 的浏览器中可用。
创建一个 Router 实例,你可以提供一个 scrollBehavior 方法:
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
})
scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
这个方法返回滚动位置的对象信息,长这样:
{ x: number, y: number }{ selector: string, offset? : { x: number, y: number }}
路由懒加载
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
const Foo = () => import( './Foo.vue')
const Bar = () => import( './Bar.vue')
const Baz = () => import('./Baz.vue')