前言

【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

v-elsev-bind="$attrs":class="classes":href="href"@click="navigate"
<slot />


</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)!
}

`useRouter`和`useRoute`都是应用`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.valueconst { length } = matched// 指标路由所匹配到的残缺路由const routeMatched: RouteRecord | undefined = matched[length - 1]const currentMatched = currentRoute.matched// 如果没有匹配到的指标路由或以后路由也没有匹配到的路由返回-1if (!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 existsconst 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
}