前言【vue-router源码】系列文章带你从0开始了解vue-router的具体实现。本系列文章源码参考vue-routerv4.0.15。源码地址:https://github.com/vuejs/router阅读本文的前提是你最好了解vue-router的基本使用。如果没有使用过,可以通过vue-router官网进行学习。本文将分析onBeforeRouteLeave和onBeforeRouteUpdate的实现。使用onBeforeRouteLeave和onBeforeRouteUpdate是vue-router提供的两个组合API,只能在setup中使用。exportdefault{setup(){onBeforeRouteLeave(){}onBeforeRouteUpdate(){}}}onBeforeRouteLeaveexportfunctiononBeforeRouteLeave(leaveGuard:NavigationGuard){//开发模式下没有组件实例,提示并返回if(__DEV__&&!getCurrentInstance()){warn('getCurrentInstance()returnednull.onBeforeRouteLeave()mustbecalledatthetopofasetupfunction')return}//matchedRouteKey在RouterView中提供,表示当前组件匹配的路由记录(通过规范化)constactiveRecord:RouteRecordNormalized|undefined=inject(matchedRouteKey,//toavoidwarning{}asany).valueif(!activeRecord){__DEV__&&warn('Noactiverouterecordwasfoundwhencalling`onBeforeRouteLeave()`.确保你在内部调用这个函数的子组件。也许你在App.vue中调用它?')return}//registerhookregisterGuard(activeRecord,'leaveGuards',leaveGuard)}因为onBeforeRouteLeave作用于组件,所以在onBeforeRouteLeave开始的时候,需要检查是否有vue实例(仅限开发环境)。如果没有实例,你会被提示并返回if(__DEV__&&!getCurrentInstance()){warn('getCurrentInstance()returnednull.onBeforeRouteLeave()mustbecalledatthetopofasetupfunction')return}然后使用inject得到一个matchedRouteKey并赋值给一个activeRecord,那么什么是activeRecord呢?constactiveRecord:RouteRecordNormalized|undefined=inject(matchedRouteKey,//toavoidwarning{}asany).value要知道activeRecord是什么,我们需要知道何时提供matchedRouteKey。因为onBeforeRouteLeave作用于路由组件,而路由组件必须是RouterView的后代,所以我们可以从RouterView中找到答案。RouterView中的设置具有以下代码行:setup(props,...){//...constinjectedRoute=inject(routerViewLocationKey)!constrouteToDisplay=computed(()=>props.route||injectedRoute.value)constdepth=inject(viewDepthKey,0)constmatchedRouteRef=computed(()=>routeToDisplay.value.matched[depth])provide(viewDepthKey,depth+1)provide(matchedRouteKey,matchedRouteRef)provide(routerViewLocationKey,routeToDisplay)//...}可以看到provide(matchedRouteKey,matchedRouteRef)是在RouterView中执行的,那么matchedRouteRef是什么呢?首先,matchedRouteRef是一个计算属性,它的返回值为routeToDisplay.value.matched[depth]。然后我们看routeToDisplay和depth,首先看routeToDisplay,routeToDisplay也是一个计算属性,它的值是props.route或者injectedRoute.value,因为props.route是用户传递过来的,所以这里我们只看injectedRoute.value,injectedRoute也是通过Inject获取到的,获取到的key就是routerViewLocationKey。看看这个键是不是有点眼熟。在安装vue-router的过程中,应用程序中注入了几个变量,其中包括routerViewLocationKey。install(app){//...app.provide(routerKey,router)app.provide(routeLocationKey,reactive(reactiveRoute))//currentRoute路由规范化对象app.provide(routerViewLocationKey,currentRoute)//...}现在我们知道routeToDisplay是当前路由的标准化对象。接下来,让我们看看什么是深度。深度也是通过inject(viewDepthKey)获取的,但是它有一个默认值,默认为0。你会发现紧接着有一行provide(viewDepthKey,depth+1),RouterView再次注入viewDepthKey,但是这次的值增加了1,为什么要这样做呢?我们知道RouterView是允许嵌套的,我们看下面的代码:0,然后向其中注入viewDepthKey,并+1;在第二层,我们可以找到viewDepthKey(第一次注入),深度为1,然后注入viewDepthKey,并且+1,此时viewDepthKey的值会覆盖第一层的注入;在第三层,我们也可以找到viewDepthKey(在第二层注入并覆盖第一层的值),此时深度为2。你找到什么了吗?depth实际上表示当前RouterView在嵌套的RouterView中的深度(从0开始)。现在我们知道了routeToDisplay和深度,让我们看看routeToDisplay.value.matched[depth]。我们知道routeToDisplay.value.matched存放的是当前路由匹配到的路由,它的顺序是父路由在子路由之前。那么index为depth的路由有什么特殊意义呢?下面看一个例子://注册路由表constrouter=createRouter({//...routes:{path:'/parent',component:Parent,name:'Parent',children:[{path:'child',name:'Child',component:Child,children:[{name:'ChildChild',path:'childchild',component:ChildChild,},],},],}})使用router.resolve({name:'ChildChild'}),打印结果,观察匹配的属性。在RouterView第一层,depth为0,matched[0]为{path:'/parent',name:'Parent',...}(这里只列出几个关键属性),level为1在二层RouterView中,depth为1,matched[1]为{path:'/parent/child',name:'Child',...},level为2。在三层RouterView中,深度为2,matched[2]为{path:'/parent/child/childchild',name:'ChildChild',...},level为3.观察到depth的值恰好为与路由的匹配顺序相同。matched[depth].name与当前解析的名称完全相同。也就是onBeforeRouteLeave中activeRecord当前组件匹配到的路由。接下来看钩子怎么注册呢?在onBeforeRouteLeave中,会调用一个registerGuard函数。registerGuard接收三个参数:record(组件匹配的标准化路由),name(hook名称,leaveGuards和updateGuards只有一个),guard(待添加的导航守卫)functionregisterGuard(record:RouteRecordNormalized,name:'leaveGuards'|'updateGuards',guard:NavigationGuard){//删除钩子的函数constremoveFromList=()=>{record[name].delete(guard)}//卸载后移除钩子onUnmounted(removeFromList)//移除钩子当keep-alive缓存的组件被去激活时onDeactivated(removeFromList)//添加当keep-alive缓存的组件被激活时的钩子onActivated(()=>{record[name].add(guard)})//添加一个hook,record[name]是一个set,record[name].add(guard)}onBeforeRouteUpdate的onBeforeRouteUpdate的实现和onBeforeRouteLeave的实现完全一样,只是调用registerGuard传递的参数不同。exportfunctiononBeforeRouteUpdate(updateGuard:NavigationGuard){if(__DEV__&&!getCurrentInstance()){warn('getCurrentInstance()返回null。必须在设置函数的顶部调用onBeforeRouteUpdate()')return}constactiveRecord:RouteRecordNormalized|undefined=inject(matchedRouteKey,//toavoidwarning{}asany).valueif(!activeRecord){__DEV__&&warn('Noactiverouterecordwasfoundwhencalling`onBeforeRouteUpdate()`确保在内部调用这个函数的子组件。也许你在App.vue中调用它?')return}registerGuard(activeRecord,'updateGuards',updateGuard)}