在SPA项目里,路由router基本是前端侧处理的,那么vue3项目中一般会怎么去写router呢,本文就来讲讲vue-router4
的一些常用写法,以及和Composition API的结合使用,同时简单讲讲实现原理,让你轻松理解前端router的原理。
基础配置
安装命令
入口引入
第一步是在main.ts的入口文件里引入router的插件
这里讲解下history这个配置
createWebHashHistory
:指的是用hash模式路由,比如/#/order/detail
这种带#
的path
其他配置还有:
createWebHistory
:基于html5原生的history API实现路由
createMemoryHistory
:基于内存信息来隐式管理路由,适用于SSR场景
除了上面main.ts入口的配置,还需要在你的App.vue
入口组件里使用<router-view>
,这样路由匹配的视图就会加载在这个组件下面。
路由配置
入口配置完了,接下来就是各个分路由怎么配置
上面的代码就是之前提到的router文件,path是页面的路径,compnent对应视图组件
一般来说还会存在动态params的情况
这里的id其实就是一个动态参数,/order/123
或/order/80EA
都能匹配上,id的格式也没做限定
如果想要id只匹配数字可以看以下写法,括号内是正则表达式,注意\
会有转义问题,需要写成\\
如果在路径上可以做一些复用,也可以参考以下写法:
这里的路径就是/order/list
和/order/detail
,不过要注意第二个path带头不能带/
,不然会被当做从根path匹配。
在构造比较复杂的应用时,推荐使用动态import
的方式引入组件,缩短各个页面首次加载的时间,并优化用户体验,代码如下:
打包的产物会进行分包,会在路由匹配的时候才会加载进来
路由守卫 - 维护登录态
有这么一个场景,后台系统设置了10天登录态失效的机制,那么10天后前端页面该怎么操作提示呢,这里的最佳实践就是结合vue-router的路由守卫
路由守卫听名字很拗口,但是看api你其实就明白是怎么回事了,router.beforeEach
,beforeEach其实就是每次在匹配路由配置的时候,在生效之前会执行的一个函数
这个代码就是一个简单的判断登录态并适配是否跳转登录页面的逻辑
Composition API
上面主要是整体系统层面的一些配置和逻辑,接下来讲讲各个独立组件里如何配合vue-router来获取路由信息,这里主要介绍Composition API的风格写法。
useRoute
setup中可以直接引用useRoute
来获取route实例,然后id就是上面配置过的动态信息
为方便使用,下面把route下的常用字段介绍下
字段名 | 含义 | 例子 |
---|---|---|
fullPath | 全路径 | /order/8223?query1=123#hash1 |
hash | 二次包装的哈希值 | #hash1 |
matched | 匹配的route配置列表Array | |
name | route中的name配置 | |
params | path的参数解析对象 | {id: “8223”} |
path | 路径 | /order/8223 |
query | query的解析对象 | {query1: “123”} |
这里还有个细节,就是useRoute可以在这个组件下个任何层级使用,不需要pinia这类工具进行公共状态管理,在实际用起来还是挺方便的
这里来看下源码其实就很容易发现原理,就是用了依赖注入的方式进行路由的状态共享
细节:
routeLocationKey
是使用Symbol作为注入名,为了防止不同库使用依赖注入导致命名冲突
这里是插件应用层的provider注册
currentRoute可以理解为对当前路由的动态映射,路由变化后会更新
useRouter
有了上面的hooks经验,那这个useRouter就很好理解了,会返回一个Router
实例,Router实例能够帮助在js中实现路由的跳转:
上面的代码可以实现从当前页面跳转到搜索页面,并带上当前页面所有的query参数
如果是不需要返回的强制跳转可以使用router.replace
route-view简易原理
这里再来讲讲vue-router
核心的组件route-view
,这个其实是实现spa单页应用路由替换的核心组件,下面的代码是我把源码精简后的伪代码,注释处都是比较核心的处理逻辑。
主要功能:
- 根据不同路由信息匹配组件
- 监听路由变化并执行
beforeEach
这类回调 - 对全局的依赖注入进行更新
defineComponent({
setup(props, { attrs, slots }) {// 类似于useRouteconst injectedRoute = inject(routerViewLocationKey)!const routeToDisplay = computed(() => props.route || injectedRoute.value)const injectedDepth = inject(viewDepthKey, 0)// 这里获取route列表匹配的第一个const depth = computed<number>(() => {let initialDepth = unref(injectedDepth)const { matched } = routeToDisplay.valuelet matchedRoutewhile ((matchedRoute = matched[initialDepth]) &&!matchedRoute.components) {initialDepth++}return initialDepth})const matchedRouteRef = computed(() => routeToDisplay.value.matched[depth.value])// 对全局提供各个参数provide(viewDepthKey,computed(() => depth.value + 1))provide(matchedRouteKey, matchedRouteRef)provide(routerViewLocationKey, routeToDisplay)// 这里是监听路由变化并调用回调,beforeEach就是这里执行的watch(() => [matchedRouteRef.value, props.name] as const,([instance, to, name], [oldInstance, from, oldName]) => {(to.enterCallbacks[name] || []).forEach(callback =>callback(instance))},{ flush: 'post' })return () => {const route = routeToDisplay.valueconst currentName = props.nameconst matchedRoute = matchedRouteRef.value// 这里找到匹配的componentconst ViewComponent =matchedRoute && matchedRoute.components![currentName]const routePropsOption = matchedRoute.props[currentName]const routeProps = routePropsOption? routePropsOption === true? route.params: typeof routePropsOption === 'function'? routePropsOption(route): routePropsOption: nullconst component = h(ViewComponent,assign({}, routeProps, attrs))return (component)}}
})
最后
为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。
有需要的小伙伴,可以点击下方卡片领取,无偿分享