路由(重要)
什么是路由?
- 后端路由:对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源;
- 前端路由:对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现;
- 在单页面应用程序中,这种通过hash改变来切换页面的方式,称作前端路由(区别于后端路由);
基础
同一个路径可以匹配多个路由,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。
起步
在 vue 中使用 vue-router(非使用模块化机制编程方式)
- 导入 vue-router 组件类库:
<!-- 1. 导入 vue-router 组件类库 -->
<script src="./lib/vue-router-2.7.0.js"></script>
- 使用
router-link
组件来导航 。
<!-- 2. 使用 router-link 组件来导航 -->
<!-- router-link默认渲染为一个a标签,可以使用tag属性改标签 -->
<router-link to="/login">登录</router-link>
<router-link to="/register">注册</router-link>
- 使用**
router-view
组件来显示匹配到的组件**
<!-- 3. 使用 router-view 组件来显示匹配到的组件 -->
<!-- 专门用来当占位符,将路由规则匹配到的组件,就会展示到这个router-view中 -->
<router-view></router-view>
- 创建组件。
var login = {
template: '<h1>登录组件</h1>'
}
var register = {
template: '<h1>注册组件</h1>'
}
- 创建一个路由 router 实例,通过
routers
属性来定义路由匹配规则- 导入vue-router包之后,在全局对象中,就有一个路由的构造函数VueRouter。
- 每个路由规则都是一个对象,这个规则对象,身上有两个必须的属性;
- 属性1是path,表示监听哪个路由链接地址;
- 属性2是component,表示如果路由是前面匹配到的path,则展示component属性对应的那个组件。
- 注意:component的属性值,必须是一个组件的模板对象,不可用是名称
redirect
路由重定向
var routerObj = new VueRouter({
// routes表示路由匹配规则
routes: [
{path: '/', redirect: '/login'},
{path: '/login', component: login},
{path: '/register', component: register}
],
linkActiveClass: 'myactive'
});
- 使用
router
属性来使用路由规则router: 路由规则对象
var vm = new Vue({
el: '#app',
data: {},
methods: {},
// 将路由规则对象,注册到vm实例上,用来监听URL地址的变化
router: routerObj
});
动态路由匹配
有时候需要使用同一组件,但渲染不同信息,比如信息模块,根据用户id来渲染不同的个人信息,但主体一样。这时候可以使用 动态路径参数 来完成。
一个路由中动态路径参数可无可多,如:/user/:username/post/:post_id
。
- 动态路径参数 以冒号
:
开头。
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
- 当匹配到一个路由时,参数值会被设置到
this.$route.params
,可以在组件内使用。用法:this.$route.params.动态路径参数名
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
注意:当使用动态路径参数的时候,如果只是动态路径参数改变的话,原来的组件会被复用,而不是重建,这意味着组件的生命周期函数不会再次被调用。
捕获所有路由或404 Not found 路由
*
匹配任何路由,通常用于客户端404错误,且一般含有通配符的路由都应该放在最后。
{
// 会匹配所有路径
path: '*'
}
当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:
// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
嵌套路由children
所谓嵌套路由就是,当一个路由匹配到一个组件的时候,该组件下又含有需要路由匹配的组件,简单来说就是父组件嵌套子组件,但这个子组件根据路由来渲染的。
前面所说,<router-view>
是用于放置路由匹配到的组件,并且是渲染最高级路由匹配到的组件。同样地,一个被渲染组件同样可以包含自己的嵌套 <router-view>
。
注意:利用children来实现子路由,子路由的path不用加/
,否则永远以根路径开始请求。大致配置方法与routes
配置一样
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}
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
}
]
}
]
})
编程式的导航
router.push(location, onComplete?, onAbort?)
在 Vue 实例内部,可以通过 $router
访问路由实例。因此想要导航到不同的 URL,则使用 router.push
方法。实际使用 <router-link>
时内部就是调用这个方法。
声明式 | 编程式 |
---|---|
<router-link :to="..."> | router.push(...) |
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
注意:如果提供了 path,params 会被忽略,例子中的 query 并不属于这种情况。
const userId = '123'
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
附:
可选的在 router.push
或 router.replace
中提供 onComplete
和 onAbort
回调作为第二个和第三个参数。这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用。
如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个
/users/1
->/users/2
),你需要使用beforeRouteUpdate
来响应这个变化 (比如抓取用户信息)。
router.replace(location, onComplete?, onAbort?)
与router.push
差不多,唯一的不同就是,它不会向history添加新记录,而是替换掉当前的history记录。
声明式 | 编程式 |
---|---|
<router-link :to="..." replace> | router.replace(...) |
router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步。
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
命名路由
命名路由就是在创建Router实例的时候,在routes
配置中给某个路由设置名称。
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
- 使用
router-link
链接一个命名路由:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
命名视图
在同时(同级)展示多个视图,而不是嵌套视图,就可以使用命名视图,为router-view
通过一个name
属性,与路由规则中的 components
里的属性名对应,如果router-view
没有设置名字,则默认是default
。
在嵌套路由中也可以使用。
注意:一个视图使用一个组件渲染,如果同个路由,多个视图就需要多个组件,则确保
comonents
配置(带上s)
<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>
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
<script src="lib/vue-router.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
.header {
background-color: yellow;
height: 100px;
}
.container {
display: flex;
height: 300px;
}
.left {
background-color: green;
flex: 2;
}
.main {
background-color: pink;
flex: 8;
}
</style>
</head>
<body>
<div id="app">
<router-view></router-view>
<!-- name属性对应router中components中的属性名 -->
<div class="container">
<router-view name="left"></router-view>
<router-view name="main"></router-view>
</div>
</div>
<script>
var header = {
template: '<h1 class="header">Header头部区域</h1>'
}
var leftBox = {
template: '<h1 class="left">Left侧边栏区域</h1>'
}
var mainBox = {
template: '<h1 class="main">mainBox主体区域</h1>'
}
var router = new VueRouter({
routes: [
{
path: '/', components: {
'default': header,
'left': leftBox,
'main': mainBox
}
}
]
});
var vm = new Vue({
el: '#app',
data: {},
methods: {},
router: router
});
</script>
</body>
</html>
重定向和别名
重定向redirect
重定向通过routes
配置redirect
属性来完成
// 从/a 重定向到 /b
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
{ path: '/c', redirect: { name: 'foo' }}
{ path: '/d', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})
别名alias
向通过routes
配置alias
属性来完成
// 当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b,URL还是 /b
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
路由组件传参
在组件中使用 $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 是静态的时候有用。
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }
]
})
函数模式
创建一个函数返回 props。这样可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。
// URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件。
const router = new VueRouter({
routes: [
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
]
})
History模式
vue-router
默认 hash 模式,但可以通过router实例配置 mode
属性来切换为history模式。
注意:history模式需要后台配置支持
导航守卫
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
记住参数或查询的改变并不会触发进入/离开的导航守卫。可以通过$route
对象来观察。
全局前置守卫
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
使用 router.beforeEach
注册一个全局前置守卫:
确保要调用 next 方法,否则钩子就不会被 resolved。
const router = new VueRouter({ ... })
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() 注册过的回调。
全局解析守卫
用 router.beforeResolve
注册一个全局守卫。和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局后置钩子
router.afterEach((to, from)
注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 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`
}
}
注意:
beforeRouteEnter
守卫 不能 访问this
,但可以通过传一个回调给next
来访问组件实例,在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。beforeRouteEnter
是支持给next
传递回调的唯一守卫。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
beforeRouteLeave
通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
beforeRouteLeave (to, from , next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给 next 的回调函数。
路由元信息meta
routes
配置中的每个路由对象为 路由记录。
一个路由匹配到的所有路由记录会暴露为 $route
对象 (还有在导航守卫中的路由对象) 的 $route.matched
数组。因此,可以遍历 $route.matched
来检查路由记录中的 meta
字段。
定义路由的时候可以配置 meta
字段:
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的动画效果用法一样
数据获取
有时候,进入某个路由后,需要向服务器获取数据用于渲染。可以在以下两种方式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据(通常为created)。在数据获取期间显示“加载中”之类的指示。
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
<template>
<div class="post">
<div class="loading" v-if="loading">
Loading...
</div>
<div v-if="error" class="error">
{{ error }}
</div>
<div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
导航完成后获取数据
export default {
data () {
return {
loading: false,
post: null,
error: null
}
},
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},
methods: {
fetchData () {
this.error = this.post = null
this.loading = true
// replace getPost with your data fetching util / API wrapper
getPost(this.$route.params.id, (err, post) => {
this.loading = false
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
}
}
在导航完成前获取数据
export default {
data () {
return {
post: null,
error: null
}
},
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()
})
},
methods: {
setData (err, post) {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
}
}
}
滚动行为
注意: 这个功能只在支持
history.pushState
的浏览器中可用。
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。可以在 router
实例内提供一个scrollBehavior
方法。
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
return { x: 0, y: 0 }
// 模仿浏览器原生表现
// 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 }}
路由懒加载
使用Vue 的 异步组件 和 Webpack的 代码分割功能 来实现组件的懒加载。
https://router.vuejs.org/zh/guide/advanced/lazy-loading.html#把组件按组分块
路由对象
- $route.path
- 类型:
string
字符串,对应当前路由的路径,总是解析为绝对路径,如 “/foo/bar”。
- 类型:
- $route.params
- 类型:
Object
一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
- 类型:
- $route.query
- 类型:
Object
一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。
- 类型:
- $route.hash
- 类型:
string
当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。
- 类型:
- $route.fullPath
- 类型:
string
完成解析后的 URL,包含查询参数和 hash 的完整路径。
- 类型:
- $route.matched
- 类型:
Array<RouteRecord>
一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)。
- 类型:
- $route.name
当前路由的名称,如果有的话。(查看命名路由) - $route.redirectedFrom
如果存在重定向,即为重定向来源的路由的名字