专注app软件定制开发【vue-router源码】十一、onBeforeRouteLeave、onBeforeRouteUpdate源码分析

【vue-rouer源码】系列文章

  1. 【vue-router源码】十一、onBeforeRouteLeave、onBeforeRouteUpdate源码分析

目录


前言

【源码】专注app软件定制开发系列文章将带你从0开始了解vue-router专注app软件定制开发的具体实现。专注app软件定制开发该系列文章源码参考vue-router v4.0.15
源码地址:
专注app软件定制开发阅读该文章的前提是你最好了解vue-router专注app软件定制开发的基本使用,如果你没有使用过的话,可通过学习下。

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

使用

onBeforeRouteLeaveonBeforeRouteUpdatevue-router提供的两个composition api,它们只能被用于setup中。

export default {  setup() {    onBeforeRouteLeave() {}        onBeforeRouteUpdate() {}  }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

onBeforeRouteLeave

export function onBeforeRouteLeave(leaveGuard: NavigationGuard) {  // 开发模式下没有组件实例,进行提示并return  if (__DEV__ && !getCurrentInstance()) {    warn(      'getCurrentInstance() returned null. onBeforeRouteLeave() must be called at the top of a setup function'    )    return  }  // matchedRouteKey是在RouterView中进行provide的,表示当前组件所匹配到到的路由记录(经过标准化处理的)  const activeRecord: 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 of a component child of <router-view>. Maybe you called it inside of App.vue?'      )    return  }  // 注册钩子  registerGuard(activeRecord, 'leaveGuards', leaveGuard)}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

因为onBeforeRouteLeave是作用在组件上的,所以onBeforeRouteLeave开头就需要检查当前是否有vue实例(只在开发环境下),如果没有实例进行提示并return

if (__DEV__ && !getCurrentInstance()) {  warn(    'getCurrentInstance() returned null. onBeforeRouteLeave() must be called at the top of a setup function'  )  return}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后使用inject获取一个matchedRouteKey,并赋给一个activeRecord,那么个activeRecord是个什么呢?

const activeRecord: RouteRecordNormalized | undefined = inject(  matchedRouteKey,  // to avoid warning  {} as any).value
  • 1
  • 2
  • 3
  • 4
  • 5

要想知道activeRecord是什么,我们就需要知道matchedRouteKey是什么时候provide的。因为onBeforeRouteLeave式作用在路由组件中的,而路由组件一定是RouterView的子孙组件,所以我们可以从RouterView中找一下答案。

RouterView中的setup有这么几行代码:

setup(props, ...) {  // ...  const injectedRoute = inject(routerViewLocationKey)!  const routeToDisplay = computed(() => props.route || injectedRoute.value)  const depth = inject(viewDepthKey, 0)  const matchedRouteRef = computed<RouteLocationMatched | undefined>(    () => routeToDisplay.value.matched[depth]  )  provide(viewDepthKey, depth + 1)  provide(matchedRouteKey, matchedRouteRef)  provide(routerViewLocationKey, routeToDisplay)  // ...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可以看到就是在RouterView中进行了provide(matchedRouteKey, matchedRouteRef)的,那么matchedRouteRef是什么呢?

首先matchedRouteRef是个,它的返回值是routeToDisplay.value.matched[depth]。接着我们看routeToDisplaydepth,先看routeToDisplayrouteToDisplay也是个计算属性,它的值是props.routeinjectedRoute.value,因为props.route使用户传递的,所以这里我们只看injectedRoute.valueinjectedRoute也是通过inject获取的,获取的key是routerViewLocationKey。看到这个key是不是有点熟悉,在vue-router进行install中向app中注入了几个变量,其中就有routerViewLocationKey

install(app) {  //...  app.provide(routerKey, router)  app.provide(routeLocationKey, reactive(reactiveRoute))  // currentRoute路由标准化对象  app.provide(routerViewLocationKey, currentRoute)  //...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

现在我们知道routeToDisplay是当前路由的标准化对象。接下来看depth是什么。depth也是通过inject(viewDepthKey)的方式获取的,但它有默认值,默认是0。你会发现紧跟着有一行provide(viewDepthKey, depth + 1)RouterView又把viewDepthKey注入进去了,不过这次值加了1。为什么这么做呢?

我们知道RouterView是允许嵌套的,来看下面代码:

<RouterView>  <RouterView>    <RouterView />  </RouterView></RouterView>
  • 1
  • 2
  • 3
  • 4
  • 5

在第一层RouterView中,因为找不到对应的viewDepthKey,所以depth是0,然后将viewDepthKey注入进去,并+1;在第二层中,我们可以找到viewDepthKey(在第一次中注入),depth为1,然后再将viewDepthKey注入,并+1,此时viewDepthKey的值会覆盖第一层的注入;在第三层中,我们也可以找到viewDepthKey(在二层中注入,并覆盖了第一层的值),此时depth为2。是不是发现了什么?depth其实代表当前RouterView在嵌套RouterView中的深度(从0开始)。

现在我们知道了routeToDisplaydepth,现在我们看routeToDisplay.value.matched[depth]。我们知道routeToDisplay.value.matched中存储的是当前路由所匹配到的路由,并且他的顺序是父路由在子路由前。那么索引为depth的路由有什么特别含义呢?我们看下面一个例子:

// 注册的路由表const router = createRouter({  // ...  routes: {    path: '/parent',    component: Parent,    name: 'Parent',    children: [      {        path: 'child',        name: 'Child',        component: Child,        children: [          {            name: 'ChildChild',            path: 'childchild',            component: ChildChild,          },        ],      },    ],  }})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
<!-- Parent --><template>  <div>    <p>parent</p>    <router-view></router-view>  </div></template><!-- Child --><template>  <div>    <p>child</p>    <router-view></router-view>  </div></template><!-- ChildChild --><template>  <div>    <p>childchild</p>  </div></template>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

使用router.resolve({ name: 'ChildChild' }),打印其结果,观察matched属性。

  1. 在第一层RouterView中,depth为0,matched[0]{path:'/parent', name: 'Parent', ...}(此处只列几个关键属性),level为1
  2. 在第二层RouterView中,depth为1,matched[1]{path:'/parent/child', name: 'Child', ...},level为2
  3. 在第三层RouterView中,depth为2,matched[2]{path:'/parent/child/childchild', name: 'ChildChild', ...},level为3

通过观察,depth的值与路由的匹配顺序刚好一致。matched[depth].name恰好与当前resolvename一致。也就是说onBeforeRouteLeave中的activeRecord当前组件所匹配到的路由。

接下来看下钩子时如何注册的?在onBeforeRouteLeave,会调用一个registerGuard函数,registerGuard接收三个参数:record(所在组件所匹配到的标准化路由)、name(钩子名,只能取leaveGuardsupdateGuards之一)、guard(待添加的导航守卫)

function registerGuard(  record: RouteRecordNormalized,  name: 'leaveGuards' | 'updateGuards',  guard: NavigationGuard) {  // 一个删除钩子的函数  const removeFromList = () => {    record[name].delete(guard)  }  // 卸载后移除钩子  onUnmounted(removeFromList)  // 被keep-alive缓存的组件失活时移除钩子  onDeactivated(removeFromList)  // 被keep-alive缓存的组件激活时添加钩子  onActivated(() => {    record[name].add(guard)  })  // 添加钩子,record[name]是个set,在路由标准化时处理的  record[name].add(guard)}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

onBeforeRouteUpdate

onBeforeRouteUpdate的实现与onBeforeRouteLeave的实现完全一致,只是调用registerGuard传递的参数不一样。

export function onBeforeRouteUpdate(updateGuard: NavigationGuard) {  if (__DEV__ && !getCurrentInstance()) {    warn(      'getCurrentInstance() returned null. onBeforeRouteUpdate() must be called at the top of a setup function'    )    return  }  const activeRecord: RouteRecordNormalized | undefined = inject(    matchedRouteKey,    // to avoid warning    {} as any  ).value  if (!activeRecord) {    __DEV__ &&      warn(        'No active route record was found when calling `onBeforeRouteUpdate()`. Make sure you call this function inside of a component child of <router-view>. Maybe you called it inside of App.vue?'      )    return  }  registerGuard(activeRecord, 'updateGuards', updateGuard)}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发