学习至官网 https://router.vuejs.org/zh/
模块化工程使用
在入口文件main.js中引用router:
import Vue from 'vue'
import App from './App'
import router from './router'
......
// 通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
new Vue({
el: '#app',
router,
......
render: h => h(App)
})
在同级router文件夹下新建index.js文件初始化router实例:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
// 定义路由
// 每个路由应该映射一个组件
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 创建 router 实例,然后传 `routes` 配置
const router = new Router({
routes,
mode: 'history'
})
export default router
导航
声明式导航
使用 router-link 组件来导航
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<!-- 当 <router-link> 对应的路由匹配成功,将自动设置 class 属性值.router-link-active -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
编程式导航
借助 router 的实例方法,通过编写代码来实现导航。
router.push
使用this.$router.push 方法导航到不同的 URL。这个方法会向 history 栈添加一个新的记录,当用户点击浏览器后退按钮时,则回到之前的 URL。
// 点击 <router-link :to="..."> 等同于调用 router.push(...)
this.$router.push(location, onComplete?, onAbort?)
onComplete:回调将会在导航成功完成 (在所有的异步钩子被解析之后)的时候进行相应的调用
onAbort:回调将会在终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用
// 参数形式
// 1.字符串
router.push('home')
// 2.对象
router.push({ path: 'home' })
// 3.命名的路由
router.push({ name: 'user', params: { userId: 123 }})
// 4.带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
// 提供了 path,params 会被忽略,如下:
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-link 组件的 to 属性。
router.replace
使用this.$router.replace 方法与router.push相同。唯一的区别是这个方法不会向 history 栈添加一个新的记录。
router.replace(location, onComplete?, onAbort?)
// 声明式
<router-link :to="..." replace>
router.go
参数是一个整数,意思是在 history 记录中向前或者后退多少步。
这些router接口都是效仿 window.history API 的
$router 与 $route
通过注入路由器,我们可以在任何组件内通过 this. $router 访问路由器,也可以通过this. $route访问当前路由。
动态路由匹配
可以使用『动态路径参数』把某种模式匹配到的所有路由都映射到同一个组件。
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
routes: [
// 动态路径参数 以冒号: 开头
// 当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用
{ path: '/user/:id', component: User }
]
可以设置多段"路径参数"。
/user/:username/post/:post_id
当使用路由参数时,两个路由渲染同个组件,原来的组件实例会被复用。这也意味着组件的生命周期钩子不会再被调用。
响应路由参数变化
想要对路由参数的变化做出响应,可以有以下两种方式:
// 第一种 用 watch 来检测 $route
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}
// 第二种 引入 beforeRouteUpdate 导航守卫
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
高阶的匹配模式
// 下面列出几种情况
routes: [
{ path: '/' },
// params are denoted with a colon ":"
{ path: '/params/:foo/:bar' },
// a param can be made optional by adding "?"
{ path: '/optional-params/:foo?' },
// a param can be followed by a regex pattern in parens
// this route will only be matched if :id is all numbers
{ path: '/params-with-regex/:id(\\d+)' },
// asterisk can match anything
{ path: '/asterisk/*' },
// make part of th path optional by wrapping with parens and add "?"
{ path: '/optional-group/(foo/)?bar' }
]
匹配优先级
有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。
路由/视图
嵌套的路由 children
App.vue中的 router-view 是最顶层的出口
<div id="app">
<router-view></router-view>
</div>
一个被渲染的组件也可以包含自己嵌套的 router-view
// 在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置
// 嵌套路径也可以以'/'开头,但是会被当做根路径,与parent的路径不会连接
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
// 提供一个空的子路由
// 当 /user/:id 匹配成功,
// UserHome 会被渲染在 User 的 <router-view> 中
{ path: '', component: UserHome },
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
命名路由 name
在routes中给某个路由设置名称。
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
// 链接一个命名路由
// 声明式
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
// 编程式
router.push({ name: 'user', params: { userId: 123 }})
命名视图
想同级展示多个视图,而不是嵌套展示的情况。
// 给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="b"></router-view>
// 一个视图需要一个组件渲染,多个视图就要在components中配置多个组件
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
嵌套的命名视图
嵌套的路由与多个命名视图的结合使用,即在children中的路由对象设置components。
路由重定向与别名
重定向
通过路由对象的redirect属性进行重定向。
// redirect 可以是如下的方法动态返回
// 或者直接使用 字符串路径/路径对象
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
导航守卫不会应用到跳转路由上,而是redirect目标上。
别名
通过路由对象的alias属性定义别名。
// 用户访问/b时,url为/b,但路由匹配到的是/a
// 别名使展示的UI结构不受限于配置的嵌套路由结构
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
路由组件传参
用组件中的props取代$route.params,使组件与路由解耦
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 }) }
]
})
路由元信息 meta
一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。
meta的使用
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-router模式 mode
hash模式
默认是hash模式,使用URL的hash来模拟完整的 URL, URL改变时页面不会重新加载。
history模式
history.pushState API 来完成 URL 跳转而无须重新加载页面。
服务器端需要增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。否则会返回404。
// nginx配置
location / {
try_files $uri $uri/ /index.html;
}
这样一来,服务器对于所有的路径都不会返回404,统一返回index.html,需要在Vue路由当中配置找不到路由时返回404页面。
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFoundComponent }
]
})
导航守卫
导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
全局守卫
全局守卫的3种方式
router.beforeEach
全局前置守卫:导航触发,进入路由之前。
router.beforeResolve
全局解析守卫:在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
router.afterEach
全局后置钩子:钩子不会接受 next 函数也不会改变导航本身。
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log('afterEach 全局后置钩子');
});
路由独享的守卫
在路由配置上直接定义 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`
// 因为当守卫执行前,组件实例还没被创建
// 可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数
next(vm => {
// 通过 `vm` 访问组件实例
})
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
// 离开守卫通常用来禁止用户在还未保存修改前突然离开,可以通过 next(false) 来取消
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
}
守卫参数
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
- next(): 进行管道中的下一个钩子。
- next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
- next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
- next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
确保要调用 next 方法,否则钩子就不会被 resolved。
完整的导航解析流程
- 导航被触发进入其他路由。
- 调用要离开路由的组件守卫beforeRouteLeave。
- 调用全局前置守卫:beforeEach。
- 在重用的组件里调用 beforeRouteUpdate。
- 调用路由独享守卫 beforeEnter。
- 解析异步路由组件。
- 在将要进入的路由组件中调用beforeRouteEnter。
- 调用全局解析守卫 beforeResolve
- 导航被确认。
- 调用全局后置钩子的 afterEach 钩子。
- 触发DOM更新(mounted)。
- 执行beforeRouteEnter 守卫中传给 next 的回调函数。
路由的数据获取
有两种获取数据的方式
导航完成之后获取
在组件本身的生命周期钩子中去获取数据
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},
导航完成之前获取
在路由进入的守卫中获取数据,获取数据成功后再进行导航
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()
})
},
路由切换时的界面滚动行为
创建Router实例时,提供一个scrollBehavior方法。
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// savedPosition:通过浏览器的前进/后退按钮时触发
// return 期望滚动到哪个的位置(两种方式)
// return { x: number, y: number }
// return { selector: string, offset? : { x: number, y: number }}
// return falsy值(布尔类型可以强制返回的「假」值)或空对象,不发生滚动
}
})
滚动方式
滚回到页面顶部
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
前进后退时模拟浏览器的原生表现
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
}
}
}
异步滚动
scrollBehavior (to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ x: 0, y: 0 })
}, 500)
})
}
路由懒加载
将不同路由对应的组件分割成不同的代码块,当路由被访问的时候才加载对应组件。
// 定义一个能够被 Webpack 自动代码分割的异步组件
const Foo = () => import('./Foo.vue')
// 在路由中正常使用
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
// 可以使用命名chunk来定义打包代码块的名称,而不是用id
// Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')