安装
npm install vue-router
简单使用
# 在 main.js 文件中
import Vue froom 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
import User from '@/views/User'
import Login from '@/views/Login'
let vueRouter = new VueRouter({
routes: [
{
path: "/"
component: Login
},
{
path: "/user",
component: User
}
]
});
let vm = new Vue({
router,
store,
render: h => h(app)
}).$mount('#app')
# 说明:
# (1)通过 Vue.use(VueRouter) 之后,会通过 VueRouter.install(Vue) 方法为每个 vue 实例混入两个生命周期钩子方法:beforeCreate, beforeDestroy。
# (2)且在 beforeCreate 中,会将 vue 实例记录到 router.apps 数组中。也会将 router,route 挂在到 vue 实例上。
# (3)所以在 vue 实例中,可以使用 this.$router 来操作路由,访问 push, repace等方法。 通过 this.$route 来获取 query, params 等路由传递数据。
动态路由匹配
动态路径
(1) 一个路径参数使用冒号 : 标记,则会认为这段路径是动态参数。
比如 /user/:userId 的 :userId 就是动态路径。
(2) 当匹配到了一个路由时,参数值会被设置到 this.$, 可以在每个组件内使用。
# 情形1: 直接在浏览器中输入url, 比如: http://localhost:8080/user/123456。
# 情形2: <router-link :to="/user/123456">查看用户详情</router-link>
当点击了 “查看用户详情” 的路由导航时,会被匹配 /user/:userId 的路径。此时会将 { userId: 123456 } 记录到 this.$route.params 中。
(3) 当匹配到一个路由时,且路由跳转时携带有 params 参数,则会将动态路径进行填充。
# 情形1: <router-link :to='{ name: "userInfo", params: { userId: 123456 } }' />
# 情形2: this.$router.push( { name: "userInfo", params: { userId: 123456 } } )
当发生路由跳转时, "/user/:userId" 会被替换成 "/user/123456" 显示到浏览器的地址栏上。
路由参数的变化
1、当新的路径发生跳转时,会与之前的 route 进行比较,尽可能的复用已经存在的组件。比较极端的情况就是:匹配到的路径是同一个,但是动态路径参数不一样
,这个时候会没有一个组件会被销毁或者创建。既然是复用已创建的组件,那么就不会有 beforeCreate, created, mount 的生命周期函数被调用。( 组件由于复用,所以不会调用与vue创建过程相关的生命周期方法。)
2、但是每一次路由跳转过程中,都会创建一个新的 route 对象。也就是说 this.$route 会响应式变化,所以可以通过监听的方式来感知跳转到了新页面。
# userInfo.vue 页面
export default {
name: "userInfo",
data(){
return {}
},
watch: {
$route( to, from ){
//对路由变化作出响应。
}
}
}
捕获全部路由
(1) 捕获全部路由
通常项目中,会有一个捕获所有路由的配置,用于对不存在或者无权限的 url 进行处理。
# 在 routes 配置内容的任意位置增加一个如下配置即可。
# 带 * 的 route config,会被自动放到 pathList 的末尾。甚至可以配置多个带 * 的 route config。
import NotFound from "@/views/not-found"
new VueRouter({
routes: [
{
#用 * 号表示后面的内容为任意长度。
path: "*",
component: NotFound
}
]
})
(2)捕获部分路由
{
#用 * 号表示后面的内容为任意长度。则匹配 /user-xxxxx 路径。
path: '/user-*'
}
匹配模式
vue-router 使用 这个文件作为路径匹配引擎。
(1) 将 routes 中的路径转为完整路径之后,会计算出对应的正则匹配表达式。
比如:{
routes: [
{
path: "/user",
component: xxxxx,
children: [{
path: ":userId",
component: xxxx
}]
}
]
}
此时 routes 会转化为两个 record 对象。
{
path: "/user",
componets: { default: component },
regex: "用于匹配/user路径的正则表达式"
....
},{
path: "/user/:userId",
components: { default: component },
regex: "用于匹配 /user/xxxx的正则表达式"
}
如果跳转的 url 为 http://localhost:8080/user/123456 的时候, 就会被 regex 所匹配。从而确定跳转路由以及对应要创建的 vue 组件。
(2)用于提取 url 上与动态路径对应的参数,放置到 params 中。
如果访问 'http://localhost:8080/user/123456', 则 "user/123456".match( regex ) 的时候,就会将 "userId" 与 '123456' 对应上。将这对 key-value 放到 this.$route.params 中。
# console.log( this.$route.params ) // { userId: 123456 }
优先级
1、Vue-router 中,对动态路由进行匹配的时候,是遍历数据 pathList 的方式,所以配置在前面的优先匹配即退出。
2、但是对于 *
号,会收集了所有的 route config 之后,会把所有path为 *
全部移到 pathList 的队尾。
嵌套路由
vue-router 支持多级路由嵌套。代码形式如下:
# router 的使用
import User from "@/views/user"
new VueRouter({
routes: [
#routes 中的元素,会被渲染到下面代码 app.vue 的 <router-view> 中。 表示是一级路由。
{
path: "/",
redirect: "/user"
},
{
path: "/user",
component: User,
#在 route config 中的路由,被称为子路由。会被渲染到父 route config 对应的 component 的 <router-view></router-view>中。
children: [{
path: ":userId",
component: UserInfo
}]
}
]
})
# app.vue 中
<template>
<div>
<view-link :to="/user/123456" />
<view-router></view-router>
</div>
</template>
export default {
name: "app"
}
# user.vue 中
<template>
<div>
12345678
<view-router></view-router>
87654321
</div>
</template>
export default {
name: "user"
}
# userInfo.vue 中
<template>
<div>
{{this.$route.params.userId}}
</div>
</template>
export default {
name: "userInfo"
}
关于 “嵌套路由” 的配置方式
# 子路由也是支持绝对路径形式配置的。
const router = new VueRouter({
routes: [
{
path: "/user,
component: User,
children: [
#形式1: 绝对路由形式。但一般会根据上级的 route config 的 path,前缀路径会保持一样。
{
#这种形式不管上级的 route config 配置路径是如何,都不受影响。
path: "/user/name",
component: Name
},
#形式2: 相对路径形式
{
#这种形式会根据上级的 route config 配置路径进行拼接。
path: ":userId",
component: UserInfo
}
#形式3: 绝对路径形式。比较怪异的就是,跟上级的 route config 的 path,前缀路径不一致。
# 之所以写上这种路径,主要还是考虑源码里面会如何处理。
{
path: "/user1/name1",
component: Name2
}
]
}
]
});
编程式导航
1、this.$( location, onComplete, onAbort )
# 假设历史记录栈的数组为: const record=["/", "/user", "/info"]; 且当前页面是指向的 "/user", 如果使用 this.$("/home"), 那么 "/user" 之前的历史记录会被清除,再新增一个 "/home",此时 record 会变为 ["/", "/user", "/home"]。
# 当然,一般而言,当前页面是指向栈定元素的。
几种使用方式
# 其实也就是 RawLocation 的几种形式
(1)RawLocation 为字符串。
this.$router.push("/home")
(2)RawLocation为对象,将 path 放到对象中。
this.$router.push({ path: 'home' })
(3)RawLocation为对象,且使用命名路由的方式。
this.$router.push( { name: 'user', params: {userId: 123} } )
(4)带参数查询方式 /user?id=123
this.$router.push( { path: "/user", query: { id: 123 } } )
(5) this.$router.push( { name: 'user', query: {userId: 123} } ) # 是否能携带 query 参数。
(6) this.$router.push( { path: "/user/:id", params: { id: 123 }}) #是否支持这种形式的动态路由。
1、如果 rawLocation 对象中,path 和 name 属性同时存在,则会忽略 path。使用命名路由的方式查找路由。
2、如果使用命名路由 name 的方式,是否可以使用 query 来携带查询参数?
3、如果使用 path 路径的方式,且使用类似 this.$router.push( { path: "/user/:id", params: { id: 123 } } ) 的方式,能够进行动态路由匹配?
4、如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用路由局部守卫 beforeRouteUpdate 来响应这个变化 (比如抓取用户信息);
也可以使用 watch: { $route( from, to ){ ... } } 来感知页面发生了跳转。
2、this.$( location, onComplete, onAbort )
replace() 的使用方式与 push() 的完全一致,就是去除历史记录栈中的位置包含了当前页面。
# 假设历史记录栈的数组为: const record=["/", "/user", "/info"]; 且当前页面是指向的 "/user", 如果使用 this.$router.push("/home"), 那么 "/user" 之前的历史记录会被清除,再新增一个 "/home",此时 record 会变为 ["/", "/home"]。
3、this.$( n )
作用:操作历史记录前进后退。正数是前进,负数是后退。
注意点:
# 使用 this.$router.go(),并不能删除历史记录栈中的元素。只能将当前页面指向历史记录栈里面的不同元素。
4、this.$()
作用: 退回到上一页。就是 this.$router.go(-1)
命名路由
1、由于存在动态路由路径的关系,通常一个 rawLocation 能匹配到好多个路由。而且 path 也存在路径长不方便书写等特点。
2、vue-router 可以通过在 rawLocation 对象中指定 name 属性来唯一对应一个路由,更加方便使用。
const router = new VueRouter({
routes: [
{
path: "/user/:userId",
name: "user",
component: User
}
]
})
# 这个 to 就是 rawLocation 对象。
<router-link :to="{ name: "user", params: {userId: 123456} }">
this.$router.push( { name: "user", params: { userId: 123456 } } )
命名视图
关于视图的几种使用方式
(1) 使用 component 属性,且值是一个对象。
import User from '@/views/User'
const vueRouter = new VuewRouter({
routes: [
{
# 使用 component 属性,且值是一个对象。
# 这种是同步加载组件的方式。会在路由初始化的过程中就引入所有的组件。 一般不推荐这么使用。
path: "/user",
component: User
}
]
})
(2) 使用 component 属性,且值是一个函数。(推荐使用)
const vueRouter = new VueRouter({
routes: [
{
# 使用 component 属性,且值是一个函数。
# 这种是异步加载组件的方式。在使用这个 component 的时候,才通过 promise.then() 的方式加载。
# 项目中基本是使用这种方式。
path: "/path",
component: ()=> import("@/views/User")
}
]
})
# 本质上 route config 中的 component 在转成 route record 对象时,会变成
{
...
components: {
default: component对象
}
}
(3) 使用 components 属性,而不是用 component
# 实际上直接使用 components 属性的行为,已经不常见了。
# 对于一个 route 只对一个组件的使用情形。
const vueRouter = new VueRouter({
routes: [
{
path: "/path",
# 这样写,等价于情形(2)。
components: {
default: ()=> import("@/views/User")
}
}
]
})
(4) 使用 component 属性,指定多个组件 (命名视图)
-
其实也不是个多难的东西,项目中也没基本见不到。(个人感觉主要还是虽然能动态加载组件,但是总体布局还是得用 v-if 来控制和处理。)
-
项目中常用 v-if 来实现某些组件的显示和隐藏。虽然这种方式比较硬编码,但是毕竟场景简单,且用的少。
# 使用方式
# 说明:
(1) 如果路径是 /home; 或者 /home/info, 则一级路由就会渲染成 "/home" 所对应的三个 component。其中 left, right是匹配对应 name 的视图。而 default 则匹配没有名称的视图。
(2) 如果路径是 /test,则 left, right 两个命名视图就不渲染。
const router = new VueRouter({
routes: [
{
path: "/home",
components: {
default: Home,
left: Bar,
right: Baz
},
children: [{
path: ":info",
component: User
}]
},
{
path: "/test",
component: Test
}
]
})
# App.vue 页面的代码
<template>
<div>
<router-view name="left"></router-view>
<router-view></router-view>
<router-view name="right"></router-view>
</div>
</template>
export default {
name: "App",
data(){
return {}
}
}
# Home.vue 页面的代码
<template>
<div>
<router-view></router-view>
<div>
</template>
export default {
name: "Home",
data(){
return {}
}
}
重定向
重定向相当于把跳转地址改为了重定向属性所指向的地址以及组件;地址栏会改变。
1、重定向使用: 任意一个 route config 都能配置 redirect 属性。
- redirect 的值就是一个字符串,作为 RawLocation 中 path 的值。
# redirect 的值就是一个字符串,作为 RawLocation 中 path 的值。
routes: [
{
path: "/",
redirect: "/home"
},
{
path: "/home",
component: Home
}
]
- redirect 的值就是一个对象(即 RawLocation 对象),对象中含有 name 或者 path。
# redirect 的值就是一个对象(即 RawLocation 对象),对象中含有 name 或者 path。
routes: [
{
path: "/",
redirect: {
name: 'home' //命名路由的方式
}
},
{
path: "/home",
name: "home" //设置 name 属性,用于命名路由跳转的匹配。
component: Home
}
]
- redirect 的值是一个函数。
# redirect 的值是一个函数。
(1) 回调函数的第一个参数是 route config 对应的 route 对象。
(2) 回调函数的返回值可以是一个字符串。或者一个对象。最终都是转为一个 RawLocation 对象。
routes: [
{
path: "/",
redirect: ( to )=> "/home"; # (to)=> ({ name: "home" })
},
{
path: "/home",
name: "home" //设置 name 属性,用于命名路由跳转的匹配。
component: Home
}
]
2、重定向原理:
(1) 对于重定向,就是根据跳转路径生成一个 RawLocation,匹配到了对应的 route record 对象。
(2) 而此时 route record 对象存在 redirect 属性,此时会把 redirct 属性的值,封装成为一个新的 RawLocation, 然后重新匹配对应的 route record 对象。
(3) 如果存在,则去生成 route 对象。
(4) 所以 url 相当于就是直接访问的 redirect 的路径,在浏览器地址栏上也会显示的是 redirect 的路径。
3、重定向注意点:
(1)如果 route config 设置了 redirect 属性,那么这个 route config 配置的 component, components 属性对应的组件不会被使用。当然在route config 中配置的组件中设置的局部导航守卫也不会被触发。
(2)在配置有 redirect 属性的 route config 上设置
别名
1、概念:
/a` 的别名是 `/b`,意味着,当用户访问 `/b` 时,URL 会保持为 `/b`,但是路由匹配则为 `/a`,就像用户访问 `/a` 一样。
2、路由配置使用
const router = new VueRouter( {
routes [{
path: "/a",
component: A,
alias: '/b'
}]
})
# 此时如果访问路径为:
(1) http://localhost:8080/a 原路径
(2) http://localhost:8080/b 别名路径
访问原路径时浏览器上就显示原路径。访问别名路径就显示别名路径。但都会访问到 A 页面。
3、原理
const router = new VueRouter({
routes: [
{
path: '/a',
component: A,
alias: '/b',
children: [{
path: "c",
component: C
}]
},
]
})
# 此时在生成 record 的时候,可以理解为数据形式变为了如下形式:
const router = new VueRouter({
routes: [
{
path: "/a",
component: A,
children: [{
path: "c",
component: C
}]
},
{
path: "/b",
component: A,
children: [{
path: "c",
component: C
}]
}
]
})
# 所以说 alias 名称不能跟兄弟 route config 中的 path 同名。
路由组件传参
Vue-router 中存在三种页面跳转传参的方式。
方式1: query 对象传参。
# query 查询参数对象传参数。 f5 刷新页面 query 数据不丢失。
(1) 可以在 url 的 ? 后面携带参数,也可以通过 query 参数设置。当然两种方式一起也行。
this.$router.push( "/test?a=1", query:{ b: 2, c: 3 });
(2) 在被跳转的页面,使用 this.$route.query 获取传递过来的参数。
let query = this.$route.query;
console.log(query)
# 猜猜两种方式同时传递相同参数,最终 a 的值是多少?
this.$router.push( "/test?a=1", query:{a:2})
方式2: param 动态路由参数对象传参。
# param 动态路由参数对象传参.
# (1)本质上而言,params 是用来填充动态路由路径的。比如 /user/:id; /user/:id/:gender。
# (2)但实际上 params 也可以传递不匹配动态路由路径的参数。通过 this.$route.params 获取。
# (3)f5刷新时,如果存在params中的属性,匹配到了动态路径并且替换成该值,那么在 f5 刷新的时候,该属性不会丢失。如果存在 params 中的属性,没有匹配到 record 中动态路径,那么在 f5 刷新的时候,该属性丢失。
比如:
const router = new VueRouter({
routes: [{
path: "/user/:id",
name: "user",
component: ()=> import("xxxxxx")
}]
})
this.$router.push({ name: "user", params: { id: 123456 } })
方式3: props 路由组件传参。
(1) 布尔模式: 将 props 设置为 true 时, route.params 将会自动设置为组件的 props.
export default {
//组件中通过 props 获取 id。
props: ['id'],
data(){ return {} }
...
}
//路由配置中,增加 props 字段,并将值设置为 true。
const routes = [{
path: "/user/:id",
component: User,
props: true
}]
# 如果存在多个命名视图,则需要对命名视图分别进行指定
const router = new VueRouter({
routes: [{
path: "/user/:id",
component: { default: User, sidebar: SideBar },
//如果存在多个命名视图,则需要对命名视图分别进行指定
props: { default: true, sidebar: false }
}]
})
(2) 对象模式,用于在 route config 中硬编码携带数据。即静态数据。(此时 param 数据不会转为 props)
export default {
name: "User",
props: ["id"]
}
const router = new VueRouter({}
routes: [{
path: "/user",
component: User,
props: { id: 123456 }
}]
)
(3) 函数模式,函数的第一个参数就是 route 实例。返回值会被填充到 属性中。
const router = new VueRouter({
routes: [{
path: "/user",
component: User,
props: route=> ( { ..., ... } )
}]
})
Html5 history 模式
html5 history 对象 (也可以支持 hash)
1、作用:
history 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录;
通过 返回当前会话的 history 状态;
2、属性:
(1)
只读,返回一个整数,该整数表示会话历史中元素的数目,包括当前加载的页。
(2)
允许 web 应用程序在历史导航上显示的设置默认滚动恢复行为。此属性可以是自动的(auto)或者手动的(manual)。
(3)
只读,返回一个表示历史堆栈顶部的状态的值。这是一种可以等待 popstate(en-us) 事件而查看状态的方法。
3、方法:
(1) ()
在浏览器里是里前往上一页,用户可以点击浏览器左上角的返回。等价于 (-1);当浏览器会话历史记录处于第一页时调用此方法没有效果,而且也不会报错。
(2) ()
在浏览器记录记录里前往下一页,用户可点击浏览器左上角的前进。等价于 (1);
(3) ( param )
通过当前页面的相对位置从浏览器历史记录(会话记录)加载页面。
==> 如果 param 为 0 或者不传,则重新载入当前页面。
==> 如果 param 为正数,则查找当前页前面的历史记录,并且前进到对应页面。(之所以有正数的情形,其实是因为页面回退过。)
==> 如果 param 为负数,则查找当前页后面的历史记录,并且后退到对应页面。
(4) (state, title, url)
()方法向浏览器历史栈添加了一个状态(增加一个记录)。pushState()方法带有三个参数:一个状态对象、一个标题(现在被忽略了)以及一个可选的 URL 地址
state object —— 状态对象是一个由 pushState()方法创建的、与历史纪录相关的 javascript 对象。当用户定向到一个新的状态时,会触发 popstate 事件。事件的 state 属性包含了历史纪录的 state 对象。如果不需要这个对象,此处可以填 null。
title —— 新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填 null。
(5) (state, title, url)
(state, title, url) 把当前的页面的历史记录替换掉.
函数参数同 ( state, title, url )
使用 history 模式
Vue-router 默认使用 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 API 来完成 URL 跳转而无须重新加载页面。 当然,如果检测到当前浏览器不支持 pushState api, vue-router也会降级为 hash 模式。
1、history 模式下,url 就像正常的 url, 一般无 # 这样的锚地。
2、history 模式下,必须要后台配置支持,否则每次 f5 刷新,就会真实的去后台请求地址,从而导致 404 返回结果。
# history 模式下的 nginx 配置:
location / {
#所有页面都跳转到 页面。
try_files $uri $uri/ /;
}
导航守卫
完整的导航解析流程:
(1)导航被触发;
(2)在失活的组件里调用 beforeRouteLeave
守卫。
(3)调用全局的 beforeEach
守卫。
(4)在重用的组件里面调用 beforeRouteUpdate
守卫。
(5)在路由配置里面调用 beforeEnter
。
(6)解析异步路由组件。
(7)在被激活的组件里调用 beforeRouteEnter
。
(8)调用全局的 beforeResolve
守卫。
(9)导航被确认。
(10)调用全局的 afterEach
钩子.
(11)触发 DOM 更新。
(12)调用 beforeRouteEnte
守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
Vue-router 提供的导航守卫主要用来通过跳转或者取消的方式守卫导航。可以理解为路由发生跳转过程中的 前置拦截
和 后置拦截
。 在 vue-router 中分为三种:
全局守卫
(1)全局前置守卫
通过 方法来注册一个全局前置拦截。
全局前置守卫可以拦截并停止路由跳转;或者重定向到其他路由路径。
const router = new VueRouter({...})
router.beforeEach( ( to, from, next )=>{
...
});
前置守卫的三个参数:
1、 to: route 实例,表示即将要进入的目标路由对象。
2、 from: route 实例,当前导航正要离开的路由。
3、 next: Function,一定要调用该方法来 resolve 这个钩子。执行效果以来 next 方法的调用参数。
(1) next(): 表示进入管道中的下一个钩子。如果全部钩子执行完成,则导航的状态就是 confirm 。
(2) next(false): 中断当前的导航。如果浏览器的 url 改变了(可能是用户手动或者浏览器后退按钮),那么 url 地址会被重置到 from 路由对应的地址。
(3) next("/"): 或者 next({path: "/"}): 跳转到不同的地址。当前的导航被纵断,然后进行一个新的导航。可以接受 router.push(xxxx) 的第一个参数对象。
(4) next("error"):如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
(2)全局解析守卫
这个用的比较少,在 vue2.5.0 之后引入。
可以使用 注册一个全局守卫。跟
类似, 区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
(3)全局后置钩子 。
当导航被确认之后,就会触发全局后置钩子的调用。但是在全局后置钩子中,不能再更改路由的跳转,所以无 next()
方法。
# 通过 router.afterEach() 方法来进行全局后置钩子的使用。
router.afterEach( ( to, from )=>{
...
} )
(4)全局错误钩子
如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 () 注册过的回调。
//全局错误钩子
(error => {
= '/login/enter'
//判断是不是找不到对应模块的 js 文件。 这问题常见测试过程中前端在发版本。
const pattern = /Loading chunk (\d)+ failed/g
const isChunkLoadFailed = (pattern)
const targetPath =
//如果错误是找不大js。就返回跳转之前的路径。
if (isChunkLoadFailed) {
(targetPath)
}
})
路由独享守卫
# 其实没感觉有啥不同,使用上跟全局守卫一样的效果。不过是配置在 options 中。
const router = new VueRouter({
routes: [{
path: "/foo",
component: Foo,
beforeEnter: ( to, from, next )=>{
//在前置钩子中一定要记得调用 next() 方法有且仅有一次。
},
beforeResolve: ( to, from, next )=>{
//在前置钩子中一定要记得调用 next() 方法有且仅有一次。
},
afterEach: ( to, from )=>{
//因为导航已经确定,不需要在使用 next() 来变更路由,所以在 afterEach() 中不需要 next 方法。
}
}]
})
组件内的守卫
需要特别注意: (1)在使用了 <keep-alive>之后的的页面,非首次进入都不会触发组件内的守卫。 (2) 如果使用 watch 监听 $route 变量,则进入或者离开都会触发。
beforeRouteEnter
#1、组件内的前置守卫的调用,发生在路由被 confirm 之前。所以此时还可以通过 next() 来修改路由。
#2、此时组件还没有被创建出来,所以 beforeRouteEnter() 方法中的 this 不存在,不是指向的组件本身。
#3、可以通过传递回调给 next() 来访问组件实例。在导航被确认后,就会执行该回调,并且会把组件实例作为回调方法的参数。
const foo = {
template: "",
beforeRouteEnter( to, from, next ){
next( vm=>{
//通过 vm 实例组件实例。
})
}
}
beforeRouteUpdate (主要特点是有 this 变量)
# 此时组件已经创建,可以使用 this 来获取 vue 实例。
const foo = {
template: "",
beforeRouteUpdate( to, from, next ){
//能使用 this 了。
this.name = to.params.name;
next();
}
}
beforeRouteLeave
# 这个路由一般用来禁止用户在还未保存修改之前突然离开。该导航可以通过 next(false) 来取消当前路由跳转。
const foo = {
template: "",
beforeRouteLeave( to, from, next ){
//禁止用户离开当前页
next(false);
}
}
路由元信息
就是定义路由的时候在每个 route config 上配置的 meta 属性。这个 meta 属性会被赋值到 ,或者是 属性上。
# 配置路由元数据
const router = new VueRouter({
routes: [
{
path: "/foo",
component: Foo,
children: [
{
path: "bar",
component: Bar,
# meta 数据最终会被赋值到 this.$route.meta 上。
meta: { keepAlive: true, requiresAuth: true }
}
]
}
]
})
# 使用元数据
# 情景1: 要跳转的页面,是否需要判断登录。
router.beforeEach( ( to, from, next )=>{
//获取是否登录的判断标示。
let toLogin = !localstorage.getItem("token");
//如果不需要登录;或者需要登录但是已经登录。
if( !to.meta.requiresAuto || !toLogin ){
next();
return;
}
//没有登录但是需要登录才能继续的页面,直接重定向到登录页。
next({
path: "/login",
query: { redirect: to.fullPath }
});
})
# 情景2: 要跳转的页面,是否需要缓存。
<template>
<div class="page">
//需要缓存的页面,则在 route config 的 meta 属性配置 keepAlive。
<div v-if="$">
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
<div v-else>
<router-view></router-view>
</div>
</div>
</template>
过渡动效
单个路由的过渡动画
# 如果要在 a 页面使用入场或者出场动画。直接如下设置即可。
# a.vue 页面
<template>
<transition :name="transitionnName">
<div class="page">
......
</div>
</transition>
</template>
export default {
beforeRouteEnter( ( to, from, next )=>{
next( (vm)=>{
vm.transitionName = "left-fade"
});
}),
beforeRouteLeave( ( to, from, next )=>{
this.transitionName = "right-fade"
})
}
基于路由的过渡动画
基于当前路由与目标路由的变化关系,动态设置过度关系。
# 使用动态的 transition name。
<template>
<transition :name="transitionName">
<router-view></router-view>
</transition>
</template>
# 监听子页面路由发生变化的时候,使用哪种过渡方式
export default {
name: "xxx",
data(){ return {} },
watch: {
"$route"( to, from ){
const toDepth = to.path.split("/").length;
const fromDepth = from.path.split("/").length;
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}
}
数据获取
没啥好记录的。
滚动行为
基本使用:
const router = new Router({
routes: [...],
//savedPosition 就是上一页面记录的滚动位置。
scrollBehavior( to, from , savedPosition ){
//返回期待滚动的位置,一般都是返回 { x:0, y:0 }
return { x: 0, y:0 }
}
})
# 返回的对象说明:
(1) 返回值可以是一个 promise 对象。源码里面会通过 () 来获取值。
(2) 返回值也可以是一个对象。
{
# 外层的 x, y 点是直接指定浏览器滚动条的位置
x: 0,
y: 0,
# 相对于锚点的相对偏移位置来计算浏览器需要滚动的位置。
selector: "锚地的选择器",
offset: {
x: 0,
y: 0
}
# 表示要浏览器进行平滑滚动。属于浏览器新增的属性。
behavior: 'smooth'
}
针对于浏览器滚动涉及到的源码基本就是下面的代码。还有一种根据锚点的相对偏移来计算滚动的更少见,不记录了。滚动行为主要是针对的一种浏览器滚动bug----当前页面存在滚动条,而下跳转到的下一页也存在滚动条,那么跳转到下一页的时候,滚动条的位置跟跳转前保持一致。所以一般用来设置新页面滚动位置是回到页面顶部。
对应的源码部分。
# 1、当 app.vue 创建时,会触发 router 实例的 init() 方法。此时会被 router.app 记录当前 vue 实例,且会开始处理 url 信息。
class VueRouter {
init(app: any /* Vue component instance */) {
...
# 如果 this.app 有值,则直接返回。则 this.app 指记录根 vue 实例。
if (this.app) {
return
}
/*
以下的代码都是创建第一个 vue 实例的时候才会被调用的。
可以理解为 是指向 这个文件的实例的。
*/
# 如果 this.app 不存在,则指向 app 实例。
this.app = app
/**
特别要注意,下面的路由跳转行为,只发生在了在浏览器输入 url 来访问 vue 项目的时候。俗称 ”第一次路由访问操作“。
*/
# 如果是 h5 的 history, 或者 hash 模式。
if (history instanceof HTML5History || history instanceof HashHistory) {
# 这个函数的调用会发生在路由跳转完成之后,作用是初始化滚动行为。
const handleInitialScroll = (routeOrError) => {
# 表示即将要跳出的 route。
const from = history.current
# 如果用户配置了 router.options.scrollBehavior 函数。
const expectScroll = this.options.scrollBehavior
# 如果当前浏览器支持 pushState 行为,且用户又配置了 router.options.scrollBehavior 函数。
const supportsScroll = supportsPushState && expectScroll
# routeOrError 存在 fullPath 属性, 且 supportsScroll 函数存在
if (supportsScroll && 'fullPath' in routeOrError) {
# 开始执行滚动操作。
handleScroll(this, routeOrError, from, false)
}
}
# 路径跳转成功,或者失败的回调。
# 如果跳转成功,则传递的参数为: route。
# 如果跳转失败,则传递的参数为: error。
const setupListeners = (routeOrError) => {
# 先调用 history 的 setupListeners()。
# (1) 作用是设置 history.scrollRestoration = "manual", 表示通过代码掌控任何所需的scroll改变。 # (2) 开始监听浏览器的 "popState" 或者 "hashchange" 事件。(如果是操作浏览器的前进,后退;是通过监听 popState 来感知前进后退。)
history.setupListeners()
#路由初始化化完成之后开始判断是否需要滚动。
handleInitialScroll(routeOrError)
}
//跳转
history.transitionTo(
//(): 返回的是访问地址字符串。
history.getCurrentLocation(),
//路径跳转成功的回调。
setupListeners,
//路径跳转失败的回调。
setupListeners
)
}
}
}
########################################################
# hash.js 源码
setupListeners(){
...
# 获取在创建 VueRouter 实例的时候是否配置有 options.scrollBehavior 属性。这个属性必须是个函数。
const expectScroll = router.options.scrollBehavior
# (1) 判断浏览器是否支持有 window.history属性,
# (2) window.history.pushState 存在,且是一个函数。
# (3) 用户是否配置了 options.scrollBehavior 属性。
# ===> 只有三个条件同时满足,才会开启 vue-router 的滚动相关设置。
const supportsScroll = supportsPushState && expectScroll;
# 如果当前浏览器支持设置 scrollBehavior,则增加浏览器滚动行为的处理。
if (supportsScroll) {
# this.listeners 不关注数组中的元素,只判断了不为空即可。
# 注意:setupScroll() 这个函数在这里是执行了的。
主要作用是设置 scrollRestoration = “manual”,以及更新了浏览器的 state。
this.listeners.push(setupScroll())
}
...
}
########################################################
# scroll.js 源码
export function setupScroll() {
#(1)history.scrollRestoration:它提供两个值 auto、manual。
# auto: 作为它的默认值,即页面跳转时的滚动行为由浏览器指定,
# manual: 意味着作为一个开发者你拥有了自主掌控任何所需的scroll改变,当用户循环往复于app的history中。如果需要,你可以跟踪scroll的位置轨迹,当你使用history.pushState(),push history的时候。
# 判断浏览器是否支持 scrollRestoration 属性。如果支持,则改为 'manual' 方式。
if ('scrollRestoration' in window.history) {
window.history.scrollRestoration = 'manual'
}
# protocolAndPath: 协议(http//https) + ip(或者域名) + 端口
const protocolAndPath = window.location.protocol + '//' + window.location.host
# absolutePath: uri + query + hash
const absolutePath = window.location.href.replace(protocolAndPath, '')
# stateCope的结构形式为:
# {
# key: "777.900"
# }
# 保护已经存在的 history state,防止被用户覆盖。
const stateCopy = extend({}, window.history.state)
# 使用当前系统时间替换掉 stateCopy 的 key 值。
stateCopy.key = getStateKey()
# 相当于更新当前的浏览器的 state。
window.history.replaceState(stateCopy, '', absolutePath)
# 开始监听 history 的 popstate 事件。
window.addEventListener('popstate', handlePopState)
return () => {
# 移除对 history 的 popstate 事件的监听。
window.removeEventListener('popstate', handlePopState)
}
}
/**
* 保存当前滚动的位置。
*/
export function saveScrollPosition() {
//获取一个根据当前时间的时间戳生成的key。
const key = getStateKey()
//如果key存在
if (key) {
//则 positionStore[key] 形式记录滚动的x,y位置。
positionStore[key] = {
x: window.pageXOffset,
y: window.pageYOffset,
}
}
}
/*
handleScroll() 函数
router: 路由对象。
to: 前往的路由
from: 上一个路由。
isPop: 是否是退出。
*/
export function handleScroll(
router: Router,
to: Route,
from: Route,
isPop: boolean
) {
//不存在 router 当前指向的 vue 实例。
if (!router.app) {
return
}
//获取创建 router 时,配置的 scrollBehavior 函数。
const behavior = router.options.scrollBehavior
//如果 scrollBehavior 没有配置,则直接返回。
if (!behavior) {
return
}
//scrollBehavior 必须是一个函数。否则警告提示。
if (process.env.NODE_ENV !== 'production') {
assert(typeof behavior === 'function', `scrollBehavior must be a function`)
}
// wait until re-render finishes before scrolling
//在下一个 event loop 周期执行一下函数。
router.app.$nextTick(() => {
//获取最后一次保存的滚动位置记录。
const position = getScrollPosition()
//shouldScroll 是对于滚动位置的配置。
const shouldScroll = behavior.call(
router,
to,
from,
isPop ? position : null
)
//如果shouldScroll 为 null。只直接返回。
if (!shouldScroll) {
return
}
//如果 shouldScroll 是 prommise 对象。
if (typeof shouldScroll.then === 'function') {
//通过 () 的方式获取要滚动的坐标。
shouldScroll
.then((shouldScroll) => {
scrollToPosition((shouldScroll: any), position)
})
.catch((err) => {
//如果不是生产环境,且产生了异常,则输出错误信息。
if (process.env.NODE_ENV !== 'production') {
assert(false, err.toString())
}
})
} else {
//滚动到指定的位置。
// shouldScroll: 是关于滚动行为的配置。
// position: 是上一次窗口滚动的位置。
scrollToPosition(shouldScroll, position)
}
})
}
/*
获取最后一次保存的滚动记录。
*/
function getScrollPosition(): ?Object {
//获取时间戳生成的 key。
const key = getStateKey()
//如果 key 存在,返回以该 key 存储的滚动位置记录。
if (key) {
return positionStore[key]
}
}
路由懒加载
路由懒加载使用
1、在 .babelrc
或者 中引入
syntax-dynamic-import
插件。
# .babelrc 中的写法:
{
"presets": xxxxx,
"plugins": ["syntax-dynamic-import"]
}
# babel.config.js 中的写法
const plugins = ["syntax-dynamic-import"];
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
plugins,
};
2、在路由配置文件中,对于每个 router config, 使用 import() 来导入组件即可。
new VueRouter({
# /* webpackChunkName: "group-foo" */ webpack在打包文件的时候,vue文件内容会被打包到 group-foo.js 文件中。当路由需要访问相应页面的时候,浏览器自动会去加载 group-foo.js 文件。
routes: [
{
name: xxxx,
path: xxxxx,
//import() 会返回一个 promise。
component: ()=> import( /* webpackChunkName: "group-foo" */ "./")
},
{
name: xxxx,
path: xxxxx,
//import() 会返回一个 promise。
component: ()=> import( /* webpackChunkName: "group-foo" */ "./")
},
{
name: xxxx,
path: xxxxx,
//import() 会返回一个 promise。
component: ()=> import( /* webpackChunkName: "group-foo" */ "./")
}
]
})
路由懒加载原理
— 待补充 —
导航故障(检测)
导航会发生故障的情形:
(1) 一个导航守卫通过调用 `next(false) `中断了这次导航。
(2) 一个导航守卫抛出了一个错误,或者调用了 `next(new Error())`
# 一般而言,在使用 $(xxx), $(xxx) 在发生路由跳转,且遇到错误的时候,可能会在控制台看到一条 "Uncaught (in promise) Error" 这样的错误,后面跟着一条更具体的消息。
检查导航故障
导航故障是一个 Error
实例, 附带了一些额外的属性。要检查一个错误是否来自于路由器,可以使用 isNavigationFailure
函数。
1、检查指定某一次路由跳转发生错误,以及错误类型。
this.$router.push( "/admin" ).catch( failure=>{
# 判断是不是重定向错误。
if( isNavigationFailure( failure, NavigationFailureType.redirected ) ){
# to, from 都是 route 实例。
console.log(failure.to.path);
console.log(failure.from.path);
}
});
2、全局检测路由跳转错误。
//全局错误钩子
(error => {
# 判断是不是重定向错误。
if( isNavigationFailure( error, ) ){
# to, from 都是 route 实例。
();
();
}
})
3、NavigationFailureType 对象说明
NavigationFailureType
可以帮助开发者来区分不同类型的导航故障。
NavigationFailureType | 帮助开发者来区分不同类型的导航故障 |
---|---|
在导航守卫中调用了 next(newLocation) 重定向到了其他地方。 |
|
在导航守卫中调用了 next(false) 中断了本次导航。 |
|
在当前导航还没有完成之前又有了一个新的导航。比如,在等待导航守卫的过程中又调用了 。 |
|
导航被阻止,因为我们已经在目标位置了。 |