花了几天终于把所有关于Vue Router3的知识点都过了一遍,中途参考了官网及对应讲师的录播课,开始表演!!!
起步
<router-link/>: 类似一个跳转页面的"按钮"
to属性的用法:
...
<router-link to="/foo">Home</router-link>
// 解析成:<a href="#/foo">Home</a>
<router-link :to="{ path: '/foo' }">Home</router-link>
// 解析成:<a href="#/foo">Home</a>
<router-link to="foo">Go to Foo</router-link>
// 不要这样写,这个地方还是path的意思,但没有/前缀,就不是根路径,不加/会发生紊乱,加上/路径层级跟清晰且不易出错
**正确用法**
<router-link :to="{ name: 'sys',params: { username: 'liuwenxiang' } }">系统默认路由</router-link>
// 有params,地址不会拼接在url中
<router-link :to="{ name: 'sys',query: { username: 'liuwenxiang' } }">系统默认路由</router-link>
// 有query,地址就拼接成 .../sys?username=liuwenxiang
<router-link :to="{ path: '/sys',query: { username: 'liuwenxiang' } }">系统默认路由</router-link> =
// 有query,地址就拼接成 .../sys?username=liuwenxiang
**错误用法**
<router-link :to="{ path: '/sys',params: { username: 'liuwenxiang' } }">系统默认路由</router-link>
// 不能通过$route.params.username取出值,因为path会覆盖params,所以path只能和query匹配!!!!
...
Tips:params类似于post请求地址栏不带参数,而query类似get请求于地址栏携带参数;name与query、params都可以搭配,path只能和query搭配使用。总之,params只能和name搭配,而params与path不能共存会覆盖。因为在路由中name不能重复,而path不具备唯一性,如果提供了path,则params会被忽略。
replace属性的用法:
<router-link :to="{ path: '/sys'}" replace></router-link>
// 调用router.replace(),而不是router.push(),即导航后不会留下此路由的 history 记录
append属性的用法:
<router-link to="sys" append>append系统默认路由</router-link>
// 前面提到过这个sys是路径,并且最好要加上/。如果没加/,可以搭配append使用,从其他路由地址栏跳转到这个路由后,路边地址变成:其他路由地址拼接这个路由也就是/sys,包括自己跳自己一样最终变成.../sys/sys,而如果使用的是to="/sys"则搭配append无效
tag属性的用法:
<router-link to="/sys" tag="li">系统默认路由</router-link>
// 不加tag属性编译成a标签,而tag="li"则编译成li标签,属性值也可以是其他标签名
exact属性的用法:
<router-link to="/sys" exact>路由1</router-link>
<router-link exact :to="{ path: '/sys',query: { username: 'liuwenxiang' } }">路由2</router-link>
// 如果不加exact,点击路由2会让路由1也加上部分 class="router-link-active"。加了exact后,点击路由不会让路由加上任何class,即"精确的class"
active-class属性的用法:
<router-link to="/sys">路由1</router-link>
<router-link exact :to="{ path: '/sys',query: { username: 'liuwenxiang' } }">路由2</router-link>
// 点击路由2,可以发现路由1,编译成<a href="#/sys" class="router-link-active">路由1</a>,加了个默认值class名: class="router-link-active"
event属性的用法:
<router-link to="/sys" event="mousemove">系统默认路由</router-link>
// 默认事件是鼠标点击,这里是mousemove鼠标划入事件,键盘事件也生效比如keyup
exact-active-class属性的用法:
<router-link to="/sys">系统默认路由</router-link>
// 点击这个路由,编译成<a href="#/sys" class="router-link-exact-active router-link-active" aria-current="page">系统默认路由</a> ,追加一个class的作用而已
aria-current-value属性的用法:
<router-link to="/sys">系统默认路由</router-link>
// 默认执行的是:<router-link to="/sys" aria-current-value="page">系统默认路由</router-link>,编译成:<a href="#/sys" class="router-link-exact-active router-link-active" aria-current="page">系统默认路由</a>,追加了aria-current="page"的属性和属性值,还有其他属性值如:step、date等
<router-view/>: 路由出口,路由渲染的地方
name属性的用法:
...
<router-view name="example"></router-view>
// 如果 `<router-view>`设置了名称,则会渲染对应的路由配置中 `components` 下的相应组件
...
...
{
path: "/sys",
components: { example: sysCpn }
},
...
| $route (路由信息对象,包含基本信息) | $router (VueRouter的实例,包含跳转方法、钩子函数等) |
|---|---|
path、pararms、query、hash、fullPath、matched、name、meta(matched:是一个数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象),图解: | push、go、replace、back、resolve、currentRoute等,图解: |
1. 动态路由匹配
应用场景: 把/user/foo 和 /user/bar映射到相同的路由
...
<router-link to="/dCpn/123" tag="button">路由1</router-link>
<router-link to="/dCpn/456" tag="button">路由2</router-link>
<router-link :to="{path:'/dCpn/789',query:{age:21}}" tag="button">路由3</router-link>
// 注意:这里params和query都存在值。$route.params:{ "name": "456" };$route.query:{ "age": "21" },这里其实也预示着path也承担着params的职责,path会覆盖params!
...
...
{ path: "/dCpn/:name", component: dCpn }
...
...
**
1.路由1组件内的值:$route.params:{ "name": "123" },可以通过$route.params.name取值。如果path: "/dCpn/:name/:age",$route.params:{ "name": xxx , "age": xxx}同存一个对象中。
2.如果dCpn的路由被渲染过了,意味着组件的生命周期钩子不会再被调用,
**
...
// 如何对路由参数的变化作出响应呢???
...
watch: {
$route(newVal, oldVal) {
// 对路由变化作出响应...
}
}
...
2. 嵌套路由
// 使用children
...
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User,
children: [
// 当 /user/:id 匹配成功,
// UserHome 会被渲染在 User 的 <router-view> 中
{ path: '', component: UserHome }
// ...其他子路由
]
}
]
})
...
3. 编程式导航
语法:
router.push(location, onComplete?, onAbort?):JS原生用法,window.history.go(n)。在 v3.2.0 中,可以通过使用 router.push 的两个可选的回调函数:onComplete 和 onAbort 来暴露导航故障
router.replace(location, onComplete?, onAbort?):JS原生用法,window.history.go(n)。同上。
router.go(n):JS原生用法,window.history.go(n)
** 声明式导航:<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' }})
** push和replace用法一样,只是replace不会在浏览器中留下记录 **
4. 命名路由
**这是通过name来标识一个路由,而一般是通过path来标识路由
<router-link :to="{ name: 'user', params: { userId: 123 }}">路由1</router-link>
// 同上:router.push({ name: 'user', params: { userId: 123 } })
5. 命名视图
...
** 同时 (同级) 展示多个视图,而不是嵌套展示,如sidebar和main两个视图,可以创造出不只有一个单独的出口**
{
path: "/sys",
components: { example: sysCpn }, // 注意components加了复数s
name: "sys"
},
...
...
<router-view name="example"></router-view>
...
6. 重定向和别名
...
**redirect 从 `/aCpn` 重定向到 `/bCpn` **
routes: [
{ path: '/aCpn', redirect: '/bCpn' }
// 其写法也可以:{ path: '/aCpn', redirect: { name: 'bCpn' }}
//{ path: '/aCpn', redirect: to => {
// // 方法接收 目标路由 作为参数 return 重定向的 字符串路径/路径对象
//}}
]
...
...
**alias 指的是url的重定向,即/a可以到达/sys的页面,只是url不一样**
{
path: "/sys",
components: { example: sysCpn },
alias: ["/b"], // 这里最好不要写成"b",而是"/b",以免造成歧义
name: "sys"
},
...
7. 路由组件传参
// $route传参params和query,让组件和路由高度耦合,于是提高了props进行参数解耦
用法1:
...
{ path: "/dCpn/:name", component: dCpn, props: true }
// 如果 `props` 被设置为 `true`,`route.params` 将会被设置为组件属性
...
...
<router-link to="/dCpn/456" tag="button">路由1</router-link>
...
...
props: {
name: {
type: String,
default: "liu_xiao",
required: true
}
} // 直接使用name,代替使用$route.params.name
...
用法2:
...
{
path: "/dCpn",
component: dCpn,
props: { name: 'world' }
},
...
...
<router-link to="/dCpn" tag="button">路由2</router-link>
...
...
props: {
name: {
type: String,
default: "liu_xiao",
required: true
}
} // 直接使用name,代替使用$route.params.name
...
8. History和Hash模式
** History模式:History模式:**
const router = new VueRouter({
mode: 'history', // 'hash'
routes: [...]
})
// 如:http://127.0.0.1:5500/src/js/2.html 没有#/。如:http://127.0.0.1:5500/src/js/2.html#/ 地址栏追加一个#/就是hash模式
// 面试被问到过:history刷新会丢,需要服务端配置404的默认已有的匹配页面。hash前端本地刷新不会丢!!!
9. 导航守卫
导航守卫是Vue Router的重点和面试必问点,所以这个部分好好总结下(指的是Vue Router3的内容)!!!
-
全局前置守卫: router.beforeEach((to, from, next) => { // ... }) // 路由跳转之前
-
全局解析守卫: router.beforeResolve((to, from, next) => { // ... }) // 和beforeEach特别类似,只是导航被确认之前,同时组件内守卫和异步路由组件被解析之后就调用解析守卫
-
全局后置钩子: router.afterEach((to, from) => { // ... }) // 路由跳转之前
-
路由独享守卫:
路由对象VueRouter中调用
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
- 组件内守卫 进入组件前:
组件内部调用
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不能获取组件实例 `this`, 因为当守卫执行前,组件实例还没被创建
}
}
- 组件内守卫 组件重新加载时:
组件内部调用
const Foo = {
template: `...`,
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用:如从"/eCpn?id=1"到"/eCpn?id=2",从"/foo/:id"到"/foo/2"
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
}
- 组件内守卫 离开组件时:
组件内部调用
const Foo = {
template: `...`,
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
Tips;to、from返回的都是$route信息;next: Function,执行效果依赖next方法的调用参数;next()、next(false)、next('/')((或者这种写法:next({ path: '/' }))、next(error)
10. 路由元信息
...
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
...
...
{ path: "/eCpn", component: eCpn, meta: { required: true } },
...
...
router.beforeEach((to, from, next) => {
// 这个部分的代码实际就是说明了权限登录,是通过meta路由元信息来判断的,组件有无这个权限字段如:require,用户有无登录来判断是否跳入登录页面
let isFlag = to.matched.some(record => record.meta.required);
if (isFlag) {
console.log("有权限");
if (!auth.login()) {
console.log("有权限且 未登录");
// next({ path: '/loginCpn'})
next({ name: "login" }); //这个next中放的时path信息而不是name信息
// return{
// path: '/loginCpn'
// }
} else {
console.log("有权限且 登录了");
next();
}
} else {
console.log("无权限");
next();
}
});
...
11. 过渡动效
...
// 这个silde和css中的class名有关联关系
<transition name="slide">
<router-view></router-view>
</transition>
...
...
<style>
.slide-enter-active,
.slide-leave-active {
transition: all .5s;
}
.slide-enter {
transform: translateX(0);
}
.slide-leave-to {
transform: translateX(200px);
}
</style>
...
12. 数据获取
应用场景: 数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。 实现逻辑时:导航完成后获取数据。而官网也提供了另一种思路:导航完成前获取数据,这里不做推荐也不做赘述!
...
// 比较常规的操作,在请求数据前和请求数据后给Boolean值取反即可
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
}
})
}
}
...
13. 滚动行为
应用场景: 当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样,更多用法参考官网
...
const router = new VueRouter({
routes, // (缩写) 相当于 routes: routes
mode: "hash",
scrollBehavior (to, from, savedPosition) {//滚动行为
if(to.path = ''){
return {x:0,y:0}
}
}
// 异步滚动
scrollBehavior (to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ x: 0, y: 0 })
}, 500)
})
}
});
...
14. 路由懒加载
应用场景:把不同路由对应的组件分割成不同的代码块(即代码分割),当路由被访问的时候才加载对应组件,优化页面加载速度
...
const Foo = () => import('./Foo.vue')
...
...
routes: [{ path: '/foo', component: Foo }]
...
15. 导航故障
个人觉得没啥用处,这里不做赘述,更多用法参考官网!!!