看了本篇你能收获什么?
- 如何显示多层级的树形菜单?嵌套路由是唯一的实现方式吗?
- 如何规避嵌套路由使用
KeepAvlie
缓存失效的问题? - vue2的路由和vue3在使用上有哪些区别?
- 哈希模式和history模式有什么区别?哈希模式就一定是使用
hashchange
吗?
一、路由简介
什么是路由?
通过监听浏览器url的改变,导航到页面中的不同视图,从而实现切换页面又不用重新加载html的效果。在实现单页面应用(SPA,single page application)的后台系统中,路由基本上是必不可少的。
VueRouter的模式
哈希模式,HashHistory Mode
它在内部传递的实际 URL 之前使用了一个哈希字符(#,如https://example.com/#/
)。由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。不过,它在 SEO 中确实有不好的影响。如果你担心这个问题,可以使用 HTML5 模式。
为什么哈希路由对SEO不友好?
因为#号后面的内容是由前端处理的,爬虫规则不会对这一部分进行任何处理。
比如https://example.com/#/foo
和https://example.com/#/bar
这两个页面在浏览器中虽然是展示两个不同的页面,但是在爬虫规则中,就只会识别到https://example.com/#/
而已。
开发过微信公众号H5页面的同学都知道,使用微信jssdk生成签名时,需要传一个url,它也是不能包含#号的,个人认为也是同一个原因。
不过,既然都开发SPA应用了,还在乎那点SEO?即使使用history模式,爬虫读到了不同的页面,拿到的也是客户端未渲染的模板字符串,没什么意义。
历史模式,Html5History mode
该模式的URL 会看起来很 “正常”,例如https://example.com/user/id
。
它出现的比哈希模式晚,是伴随着HTML5中新增的pushState
和replaceState
API才出现的,所以不支持HTML5的浏览器无法使用。
不过这点兼容基本可以忽略,HTML5规范是2014年时出的,现在基本不可能脱离HTML5特性开发的。
pushState
API比较好的一点是,传递参数是通过stateObject
,这个对象中可以包含任意类型。而哈希模式由于是通过url传参的,不仅长度会受到限制,类型也只能是字符串。
history模式下,假设qqq
是一个不存在的路由,在开发模式下表现为不渲染当前页面组件而已。但是如果打包部署到服务器后,用户在浏览器中直接访问 localhost/qqq
,就会得到一个 404 错误。
要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。
例如,我在nginx配置中添加了,
try_files $uri $uri/ /index.html;
,这样就会回退到index页面。
当然,你也可以自定义一个404页面,这样子既具有提示性,也会更美观。
抽象模式,AbstractHistory mode
用于非浏览器环境的环境下,使用一个虚拟的历史记录。比如服务端渲染,原生的跨平台应用。
该模式可以手动开启,或者当前环境为非浏览器时会自动开启。
VueRouter初始化源码概览
在我们执行new VueRouter
的时候,大概会经历以下流程:
- 如果
mode
值为空,则默认开启哈希模式 - 如果
mode
值为history
,但是当前浏览器环境不支持html5特性,则回退到哈希模式 - 如果当前为非浏览器环境,则强制回退到抽象模式
- 如果输入的值不在三种模式中,比如输入了
mode: aabb
,则抛出【无效模式】的报错
哈希模式监听事件
浏览器中存在一个事件hashchange
,当url中#号后面的部分被改变时,就会执行。按照我们正常的思维,哈希模式就是使用hashchange
,history模式才会使用pushState
等API。
但是源码中只要支持pushState
事件,就不会再用hashchange
了。
这里我查了很多资料,都没有很明确的答案解释为什么要这么做。看到之前有一个issue,也有人提了相同的疑惑,尤大在下面只是回复了特殊处理的方案,也没提到具体的原因。
如果知道原因的小伙伴,欢迎交流~
二、VueRouter实战
vue2和vue3中使用区别
这里更准确的描述是VueRouter3.x和VueRouter4.x版本的使用区别,这两个版本分别对应的是vue2.x和vue3.x。
调用方式
vue2 | vue3 | |
---|---|---|
跳转页面 | this.$router.push({name: 'xxx'}) |
import { useRouter } from 'vue-router' const router = useRouter() router.push({name: 'xxx'}) |
查看路由信息 | console.log(this.$route) |
import { useRoute } from 'vue-router' const route = useRoute() console.log(route) |
一对比起来,vue3整个使用的链路长了很多。在vue3中,先引入再声明最后才能调用,比vue2多了两步前置的。
其他区别
- Vue Router 不再是一个类,而是一组函数。
- 移动了
base
属性的配置。 - 删除了
fallback
属性。前面解析源码时提到hash模式下,如果支持pushState
就用pushState
否则才用hashchange
。这一版则是直接移除了hashchange
这个回退方案,因为vue3不再兼容太低版本的浏览器了。
// vue2
import VueRouter from 'vue-router'
new VueRouter({
base: '/myApp', // 非必填,只有服务器有额外配置才会使用
mode: 'history', // 对应hash模式和history模式
fallback: true, // vue3中已移除
})
// vue3
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
const router = createRouter({
// createWebHashHistory和createWebHistory对应hash模式和history模式
history: createWebHistory('/myApp'),
});
- transition 和 keep-alive 现在必须通过 v-slot API 在 RouterView 内部使用。
// transition 和 keep-alive 现在必须通过 v-slot API 在 RouterView 内部使用。
<router-view v-slot="{ Component }">
<transition>
<keep-alive>
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
上述变更只是部分常见的内容,更多细节可以参考官方的对比文档。router.vuejs.org/zh/guide/mi…
路由匹配
这里主要针对以下 3 种类型的路由进行举例:
静态路由
这个是最基础的路由,结构代码为:
const routes = [
{
path: '/home',
component: () => import('页面组件'),
},
];
当浏览器的url为https://example.com/home
时,他就会显示出来。
动态路由
比静态路由多了一个路径参数,该参数使用:xxx
这种格式来表示。
const routes = [
{
path: '/home/:userId ',
component: () => import('页面组件'),
},
];
和query方式的对比
当我们跳转路由时,如果希望刷新页面,还能保留路由参数。可以有以下 2 种做法:
router.push({ path: '/home', query: { userId: '123' } })
,通过route.query
取值router.push({ name: 'home', params: { userId: '123' } })
,通过route.params
取值
既然作用一样,为什么还要多增加一个动态路由?
这里查到的比较有道理的一种说法是,动态路由的意义更多是用来标识资源的唯一性。并且在vue3中,声明了动态路由,就一定要传参,否则就会直接报错。
// 给与以下路由:
const routes = [{ path: '/home/:id', name: 'home', component: xxx }]
// 缺少 `id` 参数会失败
router.push({ name: 'home' })
router.resolve({ name: 'home' })
嵌套路由
嵌套也很好理解,就是一个router-view
的组件中,还可以包着router-view
。
假设有一个根节点,里面是第一层路由
<div id="app">
<router-view></router-view>
</div>
这时候有个组件User,里面还有层路由
// 跳转到的路由为User
const routes = [{ path: '/user/:id', component: User }]
// 第二层里面还有个router-view
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`,
}
最终渲染出来的结构就是这样:
<div id="app">
<router-view>
<router-view></router-view>
</router-view>
</div>
要将组件渲染到这个嵌套的 router-view 中,我们需要在路由中配置 children:
const routes = [
{
path: '/user',
component: User,
children: [
{
// 当 /user/profile 匹配成功
// UserProfile 将被渲染到 User 的 <router-view> 内部
path: 'profile',
component: UserProfile,
},
],
},
]
如何显示多层级菜单?
假设现在有一个菜单为 3 个层级,如何设计他的路由结构呢?
看到这个树形结构,是不是第一时间就会嵌套路由?毕竟它也是一个树形结构。那对应导航栏的路由结构就可以写成:
const routes = [
{
title: '列表页',
path: '/list',
component: () => import('xxx'),
children: [
{
title: '搜索列表',
path: 'search',
component: () => import('xxx'),
children: [
{
title: '搜索列表(文章)',
path: 'article',
component: () => import('xxx'),
},
]
},
]
},
];
直接把路由组件递归一下,就可以得到下面这个html结构,最里面那层的组件也可以显示了,完美!
<router-view>
<router-view>
<router-view></router-view>
</router-view>
</router-view>
但是有一天,突然来了个需求,要求打开过的页面不允许重新加载,而是使用缓存。正常来说,我们直接使用KeepAlive
组件即可。
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
不过KeepAlive
只能保活他包裹的那一层路由,在嵌套组件中,就需要特殊处理了。
以antd pro vue的框架为例,左边为导航栏,右边为路由显示页面。无论导航栏的层级有多深,右边也是只显示一个router-view
就够了,完全用不到嵌套路由的特性。
我觉得嵌套路由的使用场景,就只有在保留父路由的部分内容,同时又在里面渲染一层子路由的时候才会用到。
那处理方式也很简单,写个函数将导航栏中的路由结构拍平即可。
antd pro框架多页签问题
使用过antd pro框架的同学都知道,按照它默认的路由配置,渲染的菜单层级超过 3 层就会有问题。下面是它官方文档的截图,只是建议路由不要超过 3 级,或者把KeepAlive关闭。
但是实际场景中,超过 3 级的菜单非常常见。而使用拍平路由结构的方式,就能完美解决这个问题。对于修改antd pro这种封装得比较多的框架来说,不需要大改它原有的组件层级,只需要调整路由数据,这是改动成本最低的方式。
参考
- router.vuejs.org/zh/guide/ VueRouter官方文档
原文链接:https://juejin.cn/post/7240666488336285752 作者:专业逮虾户aaa