共计 4259 个字符,预计需要花费 11 分钟才能阅读完成。
前言
【vue-router 源码】系列文章将带你从 0 开始理解 vue-router
的具体实现。该系列文章源码参考 vue-router v4.0.15
。
源码地址:https://github.com/vuejs/router
浏览该文章的前提是你最好理解 vue-router
的根本应用,如果你没有应用过的话,可通过 vue-router 官网学习下。
该篇文章将剖析 useRoute
、useRouter
、useLink
的实现。
应用
<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-else | |
v-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.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
}