【vue-router源码】十二、useRoute、useRouter、useLink源码分析

【vue-rouer源码】系列文章

  1. 【vue-router源码】一、router.install解析
  2. 【vue-router源码】二、createWebHistory、createWebHashHistory、createMemoryHistory源码解析
  3. 【vue-router源码】三、理解Vue-router中的Matcher
  4. 【vue-router源码】四、createRouter源码解析
  5. 【vue-router源码】五、router.addRoute、router.removeRoute、router.hasRoute、router.getRoutes源码分析
  6. 【vue-router源码】六、router.resolve源码解析
  7. 【vue-router源码】七、router.push、router.replace源码解析
  8. 【vue-router源码】八、router.go、router.back、router.forward源码解析
  9. 【vue-router源码】九、全局导航守卫的实现
  10. 【vue-router源码】十、isReady源码解析
  11. 【vue-router源码】十一、onBeforeRouteLeave、onBeforeRouteUpdate源码分析
  12. 【vue-router源码】十二、useRoute、useRouter、useLink源码分析
  13. 【vue-router源码】十三、RouterLink源码分析
  14. 【vue-router源码】十四、RouterView源码分析


前言

【vue-router源码】系列文章将带你从0开始了解vue-router的具体实现。该系列文章源码参考vue-router v4.0.15
源码地址:https://github.com/vuejs/router
阅读该文章的前提是你最好了解vue-router的基本使用,如果你没有使用过的话,可通过vue-router官网学习下。

该篇文章将分析useRouteuseRouteruseLink的实现。

使用

<script lant="ts" setup>
import { useRouter, useRoute } from 'vue-router'

// router为创建的router实例
const router = useRouter()
// currentRoute当前路由
const currentRoute = useRoute()
</script>

使用useLink可以自定义我们自己的RouterLink,如下面自定的MyRouterLink,如果是外部链接,我们需要让它新打开一个页面。

<template>
  <a
    v-if="isExternalLink"
    v-bind="$attrs"
    :class="classes"
    :href="to"
    target="_blank"
  >
    <slot />
  </a>
  <a
    v-else
    v-bind="$attrs"
    :class="classes"
    :href="href"
    @click="navigate"
  >
    <slot />
  </a>
</template>

<script lang="ts">
export default {
  name: 'MyRouterLink',
}
</script>

<script lang="ts" setup>
import { useLink, useRoute, RouterLink } from 'vue-router'
import { computed } from 'vue'

const props = defineProps({
  // @ts-ignore
  ...RouterLink.props
})

const { route, href, navigate, isActive, isExactActive  } = useLink(props)

const isExternalLink= computed(() => typeof props.to === 'string' && props.to.startsWith('http'))

const currentRoute = useRoute()

const classes = computed(() => ({
  'router-link-active':
    isActive.value || currentRoute.path.startsWith(route.value.path),
  'router-link-exact-active':
    isExactActive.value || currentRoute.path === route.value.path,
}))
</script>

MyRouterLink使用:

<my-router-link to="https://www.xxx.com">MyRouterLink External Link</my-router-link>
<my-router-link to="/home">MyRouterLink /home</my-router-link>

useRouter、useRoute

export function useRouter(): Router {
  return inject(routerKey)!
}

export function useRoute(): RouteLocationNormalizedLoaded {
  return inject(routeLocationKey)!
}

useRouteruseRoute都是使用inject来进行获取对应值。对应值都是在install过程中注入的。

install(app) {
  // ...
  app.provide(routerKey, router)
  app.provide(routeLocationKey, reactive(reactiveRoute))
  // ...
}

useLink

export function useLink(props: UseLinkOptions) {
  // router实例
  const router = inject(routerKey)!
  // 当前路由地址
  const currentRoute = inject(routeLocationKey)!

  // 目标路由相关信息
  const route = computed(() => router.resolve(unref(props.to)))

  // 被激活记录的索引
  const activeRecordIndex = computed<number>(() => {
    const { matched } = route.value
    const { length } = matched
    // 目标路由所匹配到的完整路由
    const routeMatched: RouteRecord | undefined = matched[length - 1]
    const currentMatched = currentRoute.matched
    // 如果没有匹配到的目标路由或当前路由也没有匹配到的路由返回-1
    if (!routeMatched || !currentMatched.length) return -1
    // 在当前路由所匹配到的路由中寻找目标路由
    const index = currentMatched.findIndex(
      isSameRouteRecord.bind(null, routeMatched)
    )
    if (index > -1) return index
    // 目标路由匹配到的路由的父路由的path(如果父路由是由别名产生,取源路由的path)
    const parentRecordPath = getOriginalPath(
      matched[length - 2] as RouteRecord | undefined
    )
    return (
      length > 1 &&
        // 如果目标路由的父路由与
        getOriginalPath(routeMatched) === parentRecordPath &&
        // 避免将孩子与父路由比较
        currentMatched[currentMatched.length - 1].path !== parentRecordPath
        ? currentMatched.findIndex(
            isSameRouteRecord.bind(null, matched[length - 2])
          )
        : index
    )
  })

  // 当前router-link是否处于激活状态,activeRecordIndex大于-1并且,当前路由的params与目标路由的params相同
  const isActive = computed<boolean>(
    () =>
      activeRecordIndex.value > -1 &&
      includesParams(currentRoute.params, route.value.params)
  )
  // 是否完全匹配,目标路由必须和当前路由所匹配到的路由最后一个相同
  const isExactActive = computed<boolean>(
    () =>
      activeRecordIndex.value > -1 &&
      activeRecordIndex.value === currentRoute.matched.length - 1 &&
      isSameRouteLocationParams(currentRoute.params, route.value.params)
  )

  // 利用push或replace进行路由跳转
  function navigate(
    e: MouseEvent = {} as MouseEvent
  ): Promise<void | NavigationFailure> {
    // 对于一些特殊情况,不能进行跳转
    if (guardEvent(e)) {
      return router[unref(props.replace) ? 'replace' : 'push'](
        unref(props.to)
      ).catch(noop)
    }
    return Promise.resolve()
  }

  // devtools only
  if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && isBrowser) {
    // ...
  }

  return {
    route,
    href: computed(() => route.value.href),
    isActive,
    isExactActive,
    navigate,
  }
}

在进行路由跳转时,一些特殊情况下是不能跳转的,这些情况包括:

  1. 按住了window(MAC的commond)键、alt键、ctrl键、shift键中的任一键
  2. 调用过e.preventDefault()
  3. 右键
  4. target='_blank'
function guardEvent(e: MouseEvent) {
  // don't redirect with control keys
  if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
  // don't redirect when preventDefault called
  if (e.defaultPrevented) return
  // don't redirect on right click
  if (e.button !== undefined && e.button !== 0) return
  // don't redirect if `target="_blank"`
  // @ts-expect-error getAttribute does exist
  if (e.currentTarget && e.currentTarget.getAttribute) {
    // @ts-expect-error getAttribute exists
    const target = e.currentTarget.getAttribute('target')
    if (/\b_blank\b/i.test(target)) return
  }
  // this may be a Weex event which doesn't have this method
  if (e.preventDefault) e.preventDefault()

  return true
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MAXLZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值