vue3项目怎么写路由 + 浅析vue-router4源码

889 阅读5分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

在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的方式引入组件,缩短各个页面首次加载的时间,并优化用户体验,代码如下:

image.png

打包的产物会进行分包,会在路由匹配的时候才会加载进来

image.png

路由守卫 - 维护登录态

有这么一个场景,后台系统设置了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
nameroute中的name配置
paramspath的参数解析对象{id: "8223"}
path路径/order/8223
queryquery的解析对象{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 }) {
    // 类似于useRoute
    const 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.value
      let matchedRoute
      while (
        (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.value
      const currentName = props.name
      const matchedRoute = matchedRouteRef.value
      // 这里找到匹配的component
      const ViewComponent =
        matchedRoute && matchedRoute.components![currentName]

      const routePropsOption = matchedRoute.props[currentName]
      const routeProps = routePropsOption
        ? routePropsOption === true
          ? route.params
          : typeof routePropsOption === 'function'
          ? routePropsOption(route)
          : routePropsOption
        : null

      const component = h(
        ViewComponent,
        assign({}, routeProps, attrs)
      )

      return (
        component
      )
    }
  }
})

结束

本文讲了vue-router的几个常见用法,并简单说明了下原理,当然实际的代码肯定考虑更多的兼容问题和扩展性,有兴趣的掘友可以去github看看。

创造不易,希望jym多多 点赞 + 关注 + 收藏 三连,持续更新中!!!

PS: 文中有任何错误,欢迎掘友指正

往期精彩📌

参考:

router.vuejs.org/introductio…

github1s.com/vuejs/route…

cn.vuejs.org/guide/compo…