vue-router官网:https://v3.router.vuejs.org/zh/guide/essentials/navigation.html
SPA
只有一个页面,切换页面不通过服务器刷新页面,而是通过路由和ajax。
vue路由
用 Vue.js + Vue Router 创建单页应用
入门案例
创建组件
Bar.vue
<template>
<div>bar组件</div>
</template>
Foo.vue
<template>
<div>foo组件</div>
</template>
定义路由规则
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Foo from '../views/Foo.vue'
import Bar from '../views/Bar.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/foo', // 匹配的路径
component: Foo // 匹配的组件
},
{
path: '/bar',
component: Bar
}
]
const router = new VueRouter({
routes
})
export default router
main.js挂载路由
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,//挂载路由
store,
render: h => h(App)
}).$mount('#app')
app.vue展示组件和导航
<template>
<div id="app">
<h2>APP组件</h2>
<!--router-link 组件来导航: 通过传入 `to` 属性指定链接
<router-link> 默认会被渲染成一个 `<a>` 标签
active-class:表示激活的样式
-->
<router-link to="/foo" active-class="active">foo</router-link>
<router-link to="/bar" active-class="active">bar</router-link>
<!-- -->
<!-- 路由出口 :路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>
<style lang="less">
.active{
color: red;
}
</style>
总结
1.router.js中定义路由
2.main.js中挂载路由
3.router-link定义路由导航 router-view 定义路由展示区域
4.main.js中注入路由器之后,在任何组件内通过 this.$router
访问路由器,也可以通过 this.$route
访问当前路由
动态路由匹配
其实就是restful风格的路径传参
案例
第一步:创建组件
<template>
<!-- 获取路由的路径参数 $route.params -->
<div>user组件id: {{$route.params.id}}</div>
</template>
第二步:router/index.js配置路由
import Vue from 'vue'
import VueRouter from 'vue-router'
import Foo from '../views/Foo.vue'
import Bar from '../views/Bar.vue'
import User from '../views/User.vue'
Vue.use(VueRouter)
const routes = [
//....
{
path: '/user/:id',// 动态路径参数 以冒号开头
component: User
}
]
const router = new VueRouter({
routes
})
export default router
第三步:app.vue中使用
<template>
<div id="app">
<h2>APP组件</h2>
<!--此处传递一个666参数,对应路由的id-->
<router-link to="/user/666" active-class="active">User</router-link>
<!-- -->
<!-- 路由出口 :路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>
也可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params
中。例如:
模式 | 匹配路径 | $route.params |
---|---|---|
/user/:username | /user/evan | { username: 'evan' } |
/user/:username/post/:post_id | /user/evan/post/123 | { username: 'evan', post_id: '123' } |
如果是通过?传参那么对应的值会设置到$route.query
中
<template>
<div id="app">
<h2>APP组件</h2>
<!--通过?传参那么对应的值会设置到`$route.query`-->
<router-link to="/user?id=1&name=tom" active-class="active">User</router-link>
<!-- -->
<!-- 路由出口 :路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>
响应路由参数的变化
从 /user/1
导航到 /user/2
,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子(destroyed)不会再被调用。
例如: 从666切换到777的时候,user组件的destroyed方法不会被调用
- app.vue
<router-link to="/user/666" active-class="active">User666</router-link>
<router-link to="/user/777" active-class="active">User777</router-link>
- user.vue
<template>
<!-- 获取路由的路径参数 -->
<div>user组件id: {{$route.params.id}}</div>
</template>
<script>
export default {
destroyed () {
console.log('User组件被销毁')
},
// watch: {
// $route (to, from) {
// console.log(to, from)
// }
// },
beforeRouteUpdate (to, from, next) {
console.log(to, from)
next()
}
}
</script>
两种方式: 监听路由参数的变化作出响应
第一种:watch监听
watch: {
$route (to, from) {
console.log(to, from)
}
}
第二种:使用组件内路由守卫beforeRouteUpdate
这个路由钩子
beforeRouteUpdate (to, from, next) {
console.log(to, from)
next() //千万别忘了放行
}
通配符 (*)
匹配所有。一般把这个放到最后面用来展示404错误页面
{
path: '*',
component: Error
}
匹配以xxx开头
{
path: '/user-*',
component: Test
}
当使用一个通配符时,$route.params
内会自动添加一个名为 pathMatch
参数。它包含了 URL 通过通配符被匹配的部分.也就是user-后面的部分
// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
// 给出一个路由 { path: '*' }
this.$router.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'
匹配优先级
路由定义得越早,优先级就越高。
嵌套路由(多级路由)
在路由中新增一个children数组,数组中的每一个对象就是子路由.
注意: 子路由的path不要写 /
router/index.js
{
path: '/user',
component: User,
children: [
{
path: 'add',
component: UserAdd
},
{
path: 'update',
component: UserUpdate
}
]
}
user.vue组件
<template>
<div class="main">
<h1>user组件</h1>
<router-link to="/user/add">添加用户</router-link>
<router-link to="/user/update">修改用户</router-link>
<router-view></router-view>
</div>
</template>
UserAdd.vue组件
<template>
<div>新增用户组件</div>
</template>
UserUpdate.vue组件
<template>
<div>修改用户组件</div>
</template>
命名路由
有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候
{
path: '/user/:userId',
name: 'user', //给路由起个名字
component: User
}
编程式导航
除了使用 <router-link>
创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。
router.push()
想要导航到不同的 URL,则使用 router.push
方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
当你点击 <router-link>
时,这个方法会在内部调用,所以说,点击 <router-link :to="...">
等同于调用 router.push(...)
。
声明式 | 编程式 |
---|---|
<router-link :to="..."> | router.push(...) |
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
注意:如果提供了 path
,params
会被忽略,上述例子中的 query
并不属于这种情况。
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
记忆: pp不能组合(path和params)
router.replace()
跟 router.push
很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
声明式 | 编程式 |
---|---|
<router-link :to="..." replace> | router.replace(...) |
router.go()
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)
命名视图
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view
没有设置名字,那么默认为 default
。
<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置 (带上 s):
const router = new VueRouter({
routes: [
{
path: '/',
components: { //这里变为components
default: Foo,
a: Bar, //a,b为名字和router-view中的name属性值对应
b: Baz
}
}
]
})
重定向和别名
重定向
重定向也是通过 routes
配置来完成,下面例子是从 /a
重定向到 /b
:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})
别名
“重定向”的意思是,当用户访问 /a
时,URL 将会被替换成 /b
,然后匹配路由为 /b
,那么“别名”又是什么呢?
/a
的别名是 /b
,意味着,当用户访问 /b
时,URL 会保持为 /b
,但是路由匹配则为 /a
,就像用户访问 /a
一样。
上面对应的路由配置为:
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
“别名”的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。
路由组件传参
在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。使用 props
将组件和路由解耦:(简化这种写法 $route.params.id)
布尔模式
如果 props
被设置为 true
,route.params
将会被设置为组件属性(类似父组件给子组件传值)。
const User = {
props: ['id'], //这里和父组件给子组件传值一样
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },//props设置为true
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
对象模式
props
是对象,它会被按原样设置为组件属性。当 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 })
}
]
})
URL /search?q=vue
会将 {query: 'vue'}
作为属性传递给 SearchUser
组件。
请尽可能保持 props
函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props
,请使用包装组件,这样 Vue 才可以对状态变化做出反应。
history工作模式
hash模式,页面不会重新加载,带# ,url不好看
history模式, 页面会重新加载,但是需要后端配合配置,否则就报错了。url好看
const router = new VueRouter({
mode: 'history',
routes: [...]
})
导航守卫
导航守卫主要用来通过跳转或取消的方式守卫导航。分为:全局的, 单个路由独享的,和组件级的
记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route
对象来应对这些变化,或使用 beforeRouteUpdate
的组件内守卫。例如:/user/1 和 /user/2
守卫说白了,类似拦截器
全局前置守卫
你可以使用 router.beforeEach
注册一个全局前置守卫:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
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
函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。这里有一个在用户未能验证身份时重定向到 /login
的示例:
// BAD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
// 如果用户未能验证身份,则 `next` 会被调用两次
next()
})
// GOOD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
全局后置守卫
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
router.afterEach((to, from) => {
// ...
})
路由独享守卫
你可以在路由配置上直接定义 beforeEnter
守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
这些守卫与全局前置守卫的方法参数是一样的。
组件独享守卫
可以在路由组件内直接定义以下路由导航守卫:
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`
}
}
activated和deactivated
当在router-view开启组件缓存的时候,组件不会调用destoryd生命周期方法,此时可以通过activated和deactivated这两个方法判断当前组件是激活状态还是失活状态。
<!-- 路由出口 :路由匹配到的组件将渲染在这里 -->
<keep-alive include="Foo">
<router-view></router-view>
</keep-alive>
Foo.vue
<template>
<!-- <div>foo组件:{{$route.params.id}}</div> -->
<div>foo组件:{{query}}</div>
</template>
<script>
export default {
name: 'Foo',
props: ['query'],
beforeRouteUpdate (to, from, next) {
console.log('Foo组件独享守卫--beforeRouteUpdate', to, from)
next()
},
activated () { // 组件被激活了
console.log('Foo组件activated')
},
deactivated () {
console.log('Foo组件deactivated')
}
}
</script>
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
路由元信息
定义路由的时候可以配置 meta
字段,那么如何访问这个 meta
字段呢?$route.matched
数组
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})
下面例子展示在全局导航守卫中检查元字段:
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // 确保一定要调用 next()
}
})
路由懒加载
结合 Vue 的异步组件 (opens new window)和 Webpack 的代码分割功能 (opens new window),轻松实现路由组件的懒加载。
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
webpackChunkName为打包分块的名称