exportconst RouterLinkImpl =/*#__PURE__*/defineComponent({name:'RouterLink',compatConfig:{MODE:3},props:{to:{type:[String, Object]as PropType<RouteLocationRaw>,required:true,},replace: Boolean,activeClass: String,// inactiveClass: String,exactActiveClass: String,custom: Boolean,ariaCurrentValue:{type: String as PropType<RouterLinkProps['ariaCurrentValue']>,default:'page',},},
useLink,setup(props,{ slots }){// 路由相关信息const link =reactive(useLink(props))// createRouter 中进行provideconst{ options }=inject(routerKey)!// 处理路由高亮 classconst elClass =computed(()=>({[getLinkClass(
props.activeClass,
options.linkActiveClass,'router-link-active')]: link.isActive,// [getLinkClass(// props.inactiveClass,// options.linkInactiveClass,// 'router-link-inactive'// )]: !link.isExactActive,[getLinkClass(
props.exactActiveClass,
options.linkExactActiveClass,'router-link-exact-active')]: link.isExactActive,}))// 直接返回一个渲染函数return()=>{// 获取子组件,并将link信息传入propsconst children = slots.default && slots.default(link)// 自定义标签,默认为a标签return props.custom
? children
:h('a',{'aria-current': link.isExactActive
? props.ariaCurrentValue
:null,href: link.href,// this would override user added attrs but Vue will still add// the listener, so we end up triggering bothonClick: link.navigate,class: elClass.value,},
children
)}}})
exportfunctionuseLink(props: UseLinkOptions){// 在install中 provide,获取到router信息const router =inject(routerKey)!// currentRoute始终指向当前页面路由信息const currentRoute =inject(routeLocationKey)!// 路由匹配,并返回解析结果const route =computed(()=> router.resolve(unref(props.to)))// 返回当前页面路由记录的indexconst activeRecordIndex = computed<number>(()=>{// 根据传递的参数to获取路由匹配记录const{ matched }= route.value
const{ length }= matched
constrouteMatched: RouteRecord |undefined= matched[length -1]// 当前页面路由匹配记录const currentMatched = currentRoute.matched
if(!routeMatched ||!currentMatched.length)return-1const index = currentMatched.findIndex(isSameRouteRecord.bind(null, routeMatched))if(index >-1)return index
// possible parent record// 获取路由记录的pathconst parentRecordPath =getOriginalPath(
matched[length -2]as RouteRecord |undefined)return(// we are dealing with nested routes
length >1&&// if the parent and matched route have the same path, this link is// referring to the empty child. Or we currently are on a different// child of the same parentgetOriginalPath(routeMatched)=== parentRecordPath &&// avoid comparing the child with its parent
currentMatched[currentMatched.length -1].path !== parentRecordPath
? currentMatched.findIndex(isSameRouteRecord.bind(null, matched[length -2])): index
)})// 当前路由以及父路径上的路由都会activeconst isActive = computed<boolean>(()=>
activeRecordIndex.value >-1&&includesParams(currentRoute.params, route.value.params))// 精确匹配,只有当前路由会activeconst isExactActive = computed<boolean>(()=>
activeRecordIndex.value >-1&&
activeRecordIndex.value === currentRoute.matched.length -1&&isSameRouteLocationParams(currentRoute.params, route.value.params))functionnavigate(e: MouseEvent ={}as MouseEvent): Promise<void| NavigationFailure>{// 阻止原生事件的一些默认行为if(guardEvent(e)){return router[unref(props.replace)?'replace':'push'](unref(props.to)// avoid uncaught errors are they are logged anyway).catch(noop)}return Promise.resolve()}/**
* NOTE: update {@link _RouterLinkI}'s `$slots` type when updating this
*/return{
route,href:computed(()=> route.value.href),
isActive,
isExactActive,
navigate,}}
exportconst RouterViewImpl =/*#__PURE__*/defineComponent({name:'RouterView',inheritAttrs:false,props:{name:{type: String as PropType<string>,default:'default',},route: Object as PropType<RouteLocationNormalizedLoaded>,},// Better compat for @vue/compat users// https://github.com/vuejs/router/issues/1315compatConfig:{MODE:3},// 装饰器模式相关setup(props,{ attrs, slots }){// install中进行provideconst injectedRoute =inject(routerViewLocationKey)!// 当前要展示的路由信息const routeToDisplay = computed<RouteLocationNormalizedLoaded>(()=> props.route || injectedRoute.value
)// 匹配的路由记录深度,因为matched中会包含当前路由记录以及上层记录const injectedDepth =inject(viewDepthKey,0)// 获取匹配的路由记录深度const depth = computed<number>(()=>{let initialDepth =unref(injectedDepth)const{ matched }= routeToDisplay.value
letmatchedRoute: RouteLocationMatched |undefined// 从匹配的路由记录中,查找组件不为空的索引while((matchedRoute = matched[initialDepth])&&!matchedRoute.components
){
initialDepth++}return initialDepth
})// 根据深度获取要展示的路由记录const matchedRouteRef = computed<RouteLocationMatched |undefined>(()=> routeToDisplay.value.matched[depth.value])// 向外提供信息,比如 matchedRouteKey 会在setup中使用的路由守卫inject,拿到对应的路由记录,往记录上保存守卫回调provide(
viewDepthKey,computed(()=> depth.value +1))provide(matchedRouteKey, matchedRouteRef)provide(routerViewLocationKey, routeToDisplay)// 用于监听组件是否挂载、路由记录是否改变const viewRef = ref<ComponentPublicInstance>()watch(()=>[viewRef.value, matchedRouteRef.value, props.name]asconst,([instance, to, name],[oldInstance, from, oldName])=>{// copy reused instancesif(to){// 路由跳转复用组件
to.instances[name]= instance
// 不同路由记录会复用同一个组件,但是路由记录不同,所以这里拷贝之前路由记录中,如果在setup中使用了守卫if(from && from !== to && instance && instance === oldInstance){if(!to.leaveGuards.size){
to.leaveGuards = from.leaveGuards
}if(!to.updateGuards.size){
to.updateGuards = from.updateGuards
}}}// 当监听到viewRef,即组件ref挂载后,调用beforeRouteEnter中next传入的回调,使得回调中能获取到组件实例if(
instance &&
to &&// if there is no instance but to and from are the same this might be// the first visit(!from ||!isSameRouteRecord(to, from)||!oldInstance)){;(to.enterCallbacks[name]||[]).forEach(callback=>callback(instance))}},{flush:'post'}// 组件更新后触发)// vnode方式创建组件return()=>{const route = routeToDisplay.value
// we need the value at the time we render because when we unmount, we// navigated to a different location so the value is differentconst currentName = props.name
const matchedRoute = matchedRouteRef.value
// 获取命名路由,router-view 不传入name,默认为'default'const ViewComponent =
matchedRoute && matchedRoute.components![currentName]// 没有匹配到对应路由,啥也不展示if(!ViewComponent){returnnormalizeSlot(slots.default,{Component: ViewComponent, route })}// 路由配置中开发者传递给路由组件的参数const routePropsOption = matchedRoute.props[currentName]const routeProps = routePropsOption
? routePropsOption ===true? route.params
:typeof routePropsOption ==='function'?routePropsOption(route): routePropsOption
:null// onVnodeUnmounted vnode的生命周期,类似的还有:onVnodeBeforeMount ...constonVnodeUnmounted: VNodeProps['onVnodeUnmounted']=vnode=>{// remove the instance reference to prevent leakif(vnode.component!.isUnmounted){
matchedRoute.instances[currentName]=null// 避免内存泄漏}}// 通过 vnode 格式创建匹配的路由组件const component =h(
ViewComponent,// 第一个参数可以直接传入组件assign({}, routeProps, attrs,{
onVnodeUnmounted,ref: viewRef,// ref 在这里添加}))return(// pass the vnode to the slot as a prop.// h and <component :is="..."> both accept vnodes// router-view 是否存在slot内容normalizeSlot(slots.default,{Component: component, route })||
component
)}}}/**
* 处理这种情况:
* <router-view v-slot="{ Component, route }">
* <transition :name="route.meta.transition">
* <component :is="Component" />
* </transition>
* </router-view>
*
*/// 处理route-view插槽的情况functionnormalizeSlot(slot: Slot |undefined,data: any){if(!slot)returnnull// 将data作为props传入,使得外面能通过v-slot获取到,具体查看vue文档的scope slotconst slotContent =slot(data)//slot()会返回虚拟node, slot.default默认为非命名插槽元素return slotContent.length ===1? slotContent[0]: slotContent
}
composition 守卫中获取记录并保存回调
// setup中使用的路由守卫exportfunctiononBeforeRouteLeave(leaveGuard: NavigationGuard){// matchedRouteKey在router-view中provide,返回当前匹配的路由记录constactiveRecord: RouteRecordNormalized |undefined=inject(
matchedRouteKey,// to avoid warning{}as any
).value
if(!activeRecord){
__DEV__ &&warn('No active route record was found when calling `onBeforeRouteLeave()`. Make sure you call this function inside a component child of <router-view>. Maybe you called it inside of App.vue?')return}registerGuard(activeRecord,'leaveGuards', leaveGuard)}