关于前端:vuerouter源码十一onBeforeRouteLeaveonBeforeRouteUpdate源码分析

29次阅读

共计 5407 个字符,预计需要花费 14 分钟才能阅读完成。

前言

【vue-router 源码】系列文章将带你从 0 开始理解 vue-router 的具体实现。该系列文章源码参考 vue-router v4.0.15
源码地址:https://github.com/vuejs/router
浏览该文章的前提是你最好理解 vue-router 的根本应用,如果你没有应用过的话,可通过 vue-router 官网学习下。

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

应用

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

export default {setup() {onBeforeRouteLeave() {}
    
    onBeforeRouteUpdate() {}
  }
}

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

因为 onBeforeRouteLeave 是作用在组件上的,所以 onBeforeRouteLeave 结尾就须要查看以后是否有 vue 实例(只在开发环境下),如果没有实例进行提醒并return

if (__DEV__ && !getCurrentInstance()) {
  warn('getCurrentInstance() returned null. onBeforeRouteLeave() must be called at the top of a setup function')
  return
}

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

const activeRecord: RouteRecordNormalized | undefined = inject(
  matchedRouteKey,
  // to avoid warning
  {} as any).value

要想晓得 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)
  // ...
}

能够看到就是在 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)
  //...
}

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

咱们晓得 RouterView 是容许嵌套的,来看上面代码:

<RouterView>
  <RouterView>
    <RouterView />
  </RouterView>
</RouterView>

在第一层 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,
          },
        ],
      },
    ],
  }
})
<!-- 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>

应用 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)
}

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

正文完
 0