VueRouter 原理解读 - 路由匹配器原理

1,859 阅读16分钟

一、前情回顾

在 “初始化流程” 解读的文章当中,我们分析到在createRouter初始化路由的方法当中会调用createRouterMacher方法创建页面路由匹配对象 matcher。

先来回顾下createRouterMatcher这个方法主流程逻辑:

  • 创建相关的路由匹配器的操作方法:addRoute, resolve, removeRoute, getRoutes, getRecordMatcher;
  • 根据调用createRouter方法传入的参数遍历调用addRoute初始化路由匹配器数据;
  • 将该些操作方法挂载到返回的对象属性当中;
// vuejs:router/packages/router/src/matcher/index.ts

export function createRouterMatcher(
    routes: Readonly<RouteRecordRaw[]>,
    globalOptions: PathParserOptions
): RouterMatcher {
  const matchers: RouteRecordMatcher[] = []
  const matcherMap = new Map<RouteRecordName, RouteRecordMatcher>()
  globalOptions = mergeOptions(
      { strict: false, end: true, sensitive: false } as PathParserOptions,
      globalOptions
  )

  function getRecordMatcher(name: RouteRecordName) {
    return matcherMap.get(name)
  }

  function addRoute(
      record: RouteRecordRaw,
      parent?: RouteRecordMatcher,
      originalRecord?: RouteRecordMatcher
  ) {
    // ··· ···
  }

  function removeRoute(matcherRef: RouteRecordName | RouteRecordMatcher) {
    // ··· ···
  }

  function getRoutes() {
    // ··· ···
  }

  function resolve(location: Readonly<MatcherLocationRaw>, currentLocation: Readonly<MatcherLocation>): MatcherLocation {
    // ··· ···
  }

  routes.forEach(route => addRoute(route))

  return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher }
}

当时受到文章篇幅原因,当时仅仅是了解了创建页面路由匹配器的大致逻辑,将那部分的详细逻辑原理实现像 addRoute、removeRoute 这些操作路由配置的方法实现是放到这篇文章来讲述了,接下来就正式进入到这篇文章的主题: “对路由匹配器的实现原理详解” 当中了!





二、路由匹配器的实现

路由项的结构

在对具体的几个 api 进行源码解析之前还是先来大概的了解下两个相关的数据结构定义,会对后面的具体 api 逻辑处理有一定的帮助。

路由配置项

export interface _RouteRecordBase extends PathParserOptions {
  path: string
  redirect?: RouteRecordRedirectOption
  alias?: string | string[]
  name?: RouteRecordName
  beforeEnter?:
    | NavigationGuardWithThis<undefined>
    | NavigationGuardWithThis<undefined>[]
  meta?: RouteMeta
  children?: RouteRecordRaw[]
  props?: _RouteRecordProps | Record<string, _RouteRecordProps>
}

export interface RouteRecordNormalized {
  path: _RouteRecordBase['path']                                          // 路由路径
  redirect: _RouteRecordBase['redirect'] | undefined                      // 路由跳转重定向选项
  name: _RouteRecordBase['name']                                          // 路由名称
  components: RouteRecordMultipleViews['components'] | null | undefined   // 路由视图组件
  children: RouteRecordRaw[]                                              // 子路由路径
  meta: Exclude<_RouteRecordBase['meta'], void>                           // 路由元数据
  props: Record<string, _RouteRecordProps>                                // 路由参数
  beforeEnter: _RouteRecordBase['beforeEnter']
  leaveGuards: Set<NavigationGuard>                                       // 路由离开时的守卫订阅集合
  updateGuards: Set<NavigationGuard>                                      // 路由更新时的守卫订阅集合
  enterCallbacks: Record<string, NavigationGuardNextCallback[]>           // 路由进入时的回调集合
  instances: Record<string, ComponentPublicInstance | undefined | null>   // 路由实例对象
  aliasOf: RouteRecordNormalized | undefined                              // 路由别名对应的路由记录
}

export type RouteRecord = RouteRecordNormalized

路由匹配项

interface PathParserParamKey {
  name: string
  repeatable: boolean
  optional: boolean
}

export interface PathParser {
  re: RegExp                                // 路由匹配正则
  score: Array<number[]>                    // 记录该路由匹配器的路径排名信息,后续用来计算一个 url 地址与路由配置项的匹配度
  keys: PathParserParamKey[]                // 记录该路由匹配项的关键字信息
  parse(path: string): PathParams | null    // 将 URL 地址转换为路由对象信息方法
  stringify(params: PathParams): string     // 将路由对象转换为 URL 地址方法
}

export interface RouteRecordMatcher extends PathParser {
  record: RouteRecord                       // 对应的路由配置项
  parent: RouteRecordMatcher | undefined    // 父路由匹配项
  children: RouteRecordMatcher[]            // 子路由匹配项
  alias: RouteRecordMatcher[]               // 路由别名
}

export interface MatcherLocation {
  name: RouteRecordName | null | undefined  // 路由名称
  path: string                              // 路由路径
  params: RouteParams                       // 路由 param 参数
  meta: RouteMeta                           // 路由元数据
  matched: RouteRecord[]                    // 表示与当前路由路径匹配的路由项,是一个 RouteRecord 数组
}

2.1 addRoute - 添加路由匹配项

addRoute:添加路由匹配项

  • 参数:record - 需要处理的路由项,parent - 父 matcher,originalRecord - 原始 matcher
  • 返回:matcher - 经过处理后的路由匹配项信息对象
// vuejs:router/packages/router/src/matcher/index.ts

function addRoute(
  record: RouteRecordRaw,
  parent?: RouteRecordMatcher,
  originalRecord?: RouteRecordMatcher
) {
  const isRootAdd = !originalRecord
  const mainNormalizedRecord = normalizeRouteRecord(record) // 调用 normalizeRouteRecord 格式标准化需要添加为路由项的路由配置

  mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record

  // 合并全局 VueRouter 配置与当前 addRoute 参数
  const options: PathParserOptions = mergeOptions(globalOptions, record)
  
  const normalizedRecords: (typeof mainNormalizedRecord)[] = [
    mainNormalizedRecord,
  ]

  // 判断当前路由配置是否含有 'alias' 字段,处理路由别名
  if ('alias' in record) {
    const aliases = typeof record.alias === 'string' ? [record.alias] : record.alias! // 处理路由配置项别名为数组类型
    // 遍历别名列表数组
    for (const alias of aliases) {
      // 将每一个路由别名项经过处理组合成新的标准路由配置项对象并且合并到 normalizedRecords 标准化路由配置数组中
      normalizedRecords.push(
        assign({}, mainNormalizedRecord, {
          components: originalRecord ? originalRecord.record.components : mainNormalizedRecord.components,
          path: alias,
          aliasOf: originalRecord ? originalRecord.record : mainNormalizedRecord,
        }) as typeof mainNormalizedRecord
      )
    }
  }

  let matcher: RouteRecordMatcher
  let originalMatcher: RouteRecordMatcher | undefined

  // 遍历经过前面标准化处理后的路由配置数据进行转换路由匹配器处理
  for (const normalizedRecord of normalizedRecords) {
    const { path } = normalizedRecord
    if (parent && path[0] !== '/') {
      const parentPath = parent.record.path
      const connectingSlash =
        parentPath[parentPath.length - 1] === '/' ? '' : '/'
      normalizedRecord.path =
        parent.record.path + (path && connectingSlash + path)
    }

    // 创建路由匹配项
    matcher = createRouteRecordMatcher(normalizedRecord, parent, options)

    if (originalRecord) {
      originalRecord.alias.push(matcher)
    } else {
      originalMatcher = originalMatcher || matcher
      if (originalMatcher !== matcher) originalMatcher.alias.push(matcher)
      
      if (isRootAdd && record.name && !isAliasRecord(matcher))
        removeRoute(record.name)
    }

    // 如果含有子路由则递归调用 addRoute 方法进行处理
    if (mainNormalizedRecord.children) {
      const children = mainNormalizedRecord.children
      for (let i = 0; i < children.length; i++) {
        addRoute(
          children[i],
          matcher,
          originalRecord && originalRecord.children[i]
        )
      }
    }

    originalRecord = originalRecord || matcher

    // 通过 insertMatcher 方法将路由匹配器数据增加到 matchers 和 matcherMap 当中
    if (
      (matcher.record.components &&
       Object.keys(matcher.record.components).length) ||
      matcher.record.name ||
      matcher.record.redirect
    ) {
      insertMatcher(matcher)
    }
  }

  return originalMatcher ? () => { removeRoute(originalMatcher!) } : noop
}

addRoute方法进行逻辑初步拆解后可以归纳成三部分的逻辑处理:

  1. 处理组装格式化好参数为后续转换匹配路径和添加路由项做准备;
    • 使用mergeOptions方法合并 VueRouter 全局配置与addRoute参数与创建options对象变量;
    • 使用normalizeRouteRecord格式标准化需要添加为路由项的路由配置后封装到一个数组当中并将该数组指向为normalizedRecords变量;
  1. 判断当前要处理的路由配置项是否有alias别名字段进行别名的处理;
    • 使用in判断需要添加的路由配置项是否有alias别名属性,如果存在则继续进行执行处理路由别名的逻辑;
    • 处理该路由配置项的别名数据为数组形式,支持接下来的遍历路由别名数组操作;
    • 遍历别名列表数组,将每一个路由别名项经过处理组合成新的标准路由配置项对象并且合并到 normalizedRecords 标准化路由配置数组中;
  1. 遍历标准化处理路由配置数组,将每一项路由配置项转换为路由匹配器数据:
    • 首先,生成普通路由和嵌套路由的path,然后调用createRouteRecordMatcher方法生成一个路由匹配项记录;
    • 判断如果有children子路由项则进行addRoute的递归调用对子路由进行路由匹配器转换处理;
    • 处理好相关的路由匹配项数据后则使用insertMatcher方法将当前的路由匹配项数据 matcher 添加到 matchers 数组和 matcherMap 当中。

接下来看看createRouteRecordMatcher这个创建路由匹配项数据的方法的具体实现:

// vuejs:router/packages/router/src/matcher/pathMatcher.ts

export function createRouteRecordMatcher(
  record: Readonly<RouteRecord>,
  parent: RouteRecordMatcher | undefined,
  options?: PathParserOptions
): RouteRecordMatcher {
  const parser = tokensToParser(tokenizePath(record.path), options)
  
  const matcher: RouteRecordMatcher = assign(parser, {
    record,
    parent,
    children: [],
    alias: [],
  })

  if (parent) {
    if (!matcher.record.aliasOf === !parent.record.aliasOf)
      parent.children.push(matcher)
  }

  return matcher
}
  • createRouteRecordMatcher这个方法其实主要就是调用了tokensToParser方法获取一个经过处理的路由路由转换器数据并且将相关数据合并到这个转换器对象上创建新的matcher路由匹配器对象;
    • tokensToParser方法主要是对路由配置的path路由路径通过编码、解码的方式变化到一个 token 数组、score 权重数组(该tokensToParser方法对路由路径的匹配解析操作较为复杂,后续有机会再单独抽取进行讲解),让后续能够根据这个 token 数组来进行辨认并处理子路由、动态路由、路由参数等操作和根据这个 score 来判定该路由项的权重优先级;
  • 接着判断是否有父路由对象parent,存在父路由的情况则进行关联操作;
  • 最后将这个matcher路由匹配器对象作为函数返回值返回。

在根据路由配置项通过createRouteRecordMatcher转换为路由匹配项数据后会调用insertMatcher方法将这个路由匹配项存储起来,接着我们再详细看看这个insertMatcher添加路由匹配项数据方法的实现:

// vuejs:router/packages/router/src/matcher/index.ts

function insertMatcher(matcher: RouteRecordMatcher) {
  let i = 0

  // 通过 while 不断遍历和判断路由匹配项的优先级的操作来找到该路由匹配项数组插入位置的下标
  while (
    i < matchers.length &&
    comparePathParserScore(matcher, matchers[i]) >= 0 &&
    (matcher.record.path !== matchers[i].record.path ||
     !isRecordChildOf(matcher, matchers[i]))
  )
    i++

  // 通过 splice 对 matchers 数组进行路由匹配项插队保存操作
  matchers.splice(i, 0, matcher)

  // 通过 set 对 matcherMap Map 进行对应路由匹配项设置
  if (matcher.record.name && !isAliasRecord(matcher))
    matcherMap.set(matcher.record.name, matcher)
}

insertMatcher方法将路由匹配器保存到matchers数组和matcherMapmap对象前会调用comparePathParserScore方法对该需要保存的路由匹配项数据进行一个权重计算:

  • 这个权重计算主要是根据前面经过tokensToParser处理后得出的 score 权重分数数组来判断以及路由路径长度来计算得出,这块后续有机会再一起和tokensToParser抽取进行讲解;

在遍历排序权重后获取到数组下标后进行保存数据处理:

  • 通过 splice 对 matchers 数组进行路由匹配项插队保存操作(会将权重大的插入到 matchers 数组中前面的位置);
  • 通过 set 对 matcherMap Map 进行对应路由匹配项设置;

提前的理解阅读:这里为啥要区分权重呢?

通过区分权重来决定 matchers 数组中对应路由项的下标,在 comparePathParserScore 的权重排序逻辑当中我们能够知道权重越大,路由项元素在 matchers 当中位置越后,因此在 matchers.find 相关查找(例如即将要讲到的 resolve 方法)当中会更先更快遍历更少次数就能被查找到,这个也是 VueRouter 当中对性能优化的一个小体现。

至此,这个addRoute方法实现的逻辑基本上已经解析完成。

2.2 removeRoute - 删除路由项

removeRoute:根据参数查找对应路由项并且删除该路由项

  • 参数:matcherRef - 路由标识,路由项名字或是路由项对象
  • 返回:void
// vuejs:router/packages/router/src/matcher/index.ts

function removeRoute(matcherRef: RouteRecordName | RouteRecordMatcher) {
  if (isRouteName(matcherRef)) {
    const matcher = matcherMap.get(matcherRef)
    if (matcher) {
      matcherMap.delete(matcherRef)
      matchers.splice(matchers.indexOf(matcher), 1)
      matcher.children.forEach(removeRoute)
      matcher.alias.forEach(removeRoute)
    }
  } else {
    const index = matchers.indexOf(matcherRef)
    if (index > -1) {
      matchers.splice(index, 1)
      if (matcherRef.record.name) matcherMap.delete(matcherRef.record.name)
      matcherRef.children.forEach(removeRoute)
      matcherRef.alias.forEach(removeRoute)
    }
  }
}

能够看到其实删除路由匹配项记录的操作removeRoute的逻辑其实并不难理解:

  • 首先是根据参数的类型分别处理不同的查找对应路由匹配项;
  • 找到对应的路由匹配项后调用deletesplice删除 Map 和数组中的路由配置项;
  • 然后再递归调用removeRoute删除其对应的别名路由和子路由匹配项。

2.3 resolve - 获取路由的一些标准化信息

resolve:根据参数来获取对应路由的一些标准化的信息对象

  • 参数:location - 要标准化处理的路由信息,currentLocation - 当前的标准化路由对象
  • 返回:matcherLocation - 标准化的路由对象信息
// vuejs:router/packages/router/src/matcher/index.ts

function resolve(
  location: Readonly<MatcherLocationRaw>,
  currentLocation: Readonly<MatcherLocation>
): MatcherLocation {
  let matcher: RouteRecordMatcher | undefined
  let params: PathParams = {}
  let path: MatcherLocation['path']
  let name: MatcherLocation['name']

  // 根据参数走不同的逻辑查找到对应的路由匹配项与处理获取相关字段信息
  if ('name' in location && location.name) { // 根据路由名字查找路由匹配对象 matcher
    matcher = matcherMap.get(location.name)

    name = matcher.record.name
    params = assign(
      paramsFromLocation(
        currentLocation.params,
        matcher.keys.filter(k => !k.optional).map(k => k.name)
      ),
      location.params &&
      paramsFromLocation(
        location.params,
        matcher.keys.map(k => k.name)
      )
    )
    path = matcher.stringify(params)
  } else if ('path' in location) { // 根据路由路径查找路由匹配对象 matcher
    path = location.path

    matcher = matchers.find(m => m.re.test(path))

    if (matcher) {
      params = matcher.parse(path)!
      name = matcher.record.name
    }
  } else { // 根据当前的路由来查找对应的路由匹配对象 matcher
    matcher = currentLocation.name
      ? matcherMap.get(currentLocation.name)
      : matchers.find(m => m.re.test(currentLocation.path))

    name = matcher.record.name
    params = assign({}, currentLocation.params, location.params)
    path = matcher.stringify(params)
  }

  const matched: MatcherLocation['matched'] = []
  let parentMatcher: RouteRecordMatcher | undefined = matcher

  // 遍历通过不同形式获取到的路由匹配器数据构造出有父子关系的路由信息
  while (parentMatcher) {
    matched.unshift(parentMatcher.record) // 将当前的路由匹配项对应的路由配置项推入到 matched 数组内
    parentMatcher = parentMatcher.parent // 重新赋值父路由匹配项进行下一轮循环直到找到根的匹配项
  }

  return {
    name,
    path,
    params,
    matched,
    meta: mergeMetaFields(matched),
  }
}

从上面的resolve的源码当中能够看到经过对 if 判断逻辑进行划分类型处理后这个resolve获取信息的实现条例逻辑也是比较清晰了。

  • 会优先通过name或者path两个属性对路由匹配器数据当中寻找对应的路由匹配项及相关信息;
    • 根据 name 直接从matcherMap map 对象中使用getapi 通过路由名称获取对应的路由匹配项。
    • 根据 path 则需要遍历matchers路由匹配器数组一个个通过正则test方法对路由匹配项的reg与需要处理的路由项的path进行匹配查找。
  • 如果都没有对应属性则直接使用第二个参数传入的当前路由项(同样也是使用当前路由的namepath属性来获取路由匹配器)进行处理获取相关的信息。

获取到对应的路由匹配项信息后进一步处理:

  • 通过遍历路由匹配器数据构造出有父子关系的路由信息。
  • 使用mergeMetaFields遍历合并路由配置项meta字段内的属性并作为同名meta属性抛出。

2.4 getRoutes 与 getRecordMatcher - 获取路由匹配器信息

getRoutes:获取所有路由匹配项数据

  • 参数:void
  • 返回:matchers - 所有路由匹配项数据的数组

getRecordMatcher:根据路由项名字参数查找对应的路由匹配项并且返回

  • 参数:name - 路由项名字
  • 返回:RouteRecordMatcher - 路由匹配项
// vuejs:router/packages/router/src/matcher/index.ts

export function createRouterMatcher(
    routes: Readonly<RouteRecordRaw[]>,
    globalOptions: PathParserOptions
): RouterMatcher {
  const matchers: RouteRecordMatcher[] = []
  const matcherMap = new Map<RouteRecordName, RouteRecordMatcher>()
  globalOptions = mergeOptions(
      { strict: false, end: true, sensitive: false } as PathParserOptions,
      globalOptions
  )

  function getRecordMatcher(name: RouteRecordName) {
    return matcherMap.get(name)
  }

  function getRoutes() {
    return matchers
  }

  return { getRoutes, getRecordMatcher }
}

源码的实现也是很简单的,就是在前面的已经通过addRoute的初始化处理相关的路由匹配器信息和相关变量的基础上,getRoutes能够直接返回完整的一个路由匹配器数组数据;而getRecordMatcher则使用 Map 数据结构的get方法通过传入的名字参数去搜索到对应的记录对象从而返回对应的一个路由匹配器对象数据。





三、路由匹配器的使用

3.1 内部维护路由配置信息

在 VueRouter 这个前端路由管理库当中在初始化创建全局路由对象createRouter方法接受了相关的路由配置routes后会在内部使用createRouterMatcher方法对这份路由配置信息进行转换创建为对应的路由匹配器信息,在后续路由跳转对路由 url 进行解析基本都是基于这份路由匹配器数据进行;并且在初始化时候将相关路由匹配器数据的操作方法经过封装后挂载到这个全局路由对象上进行抛出外部调用。

在前面的“初始化流程”的文章当中也有提及到这块的一部分,初始化时候会通过createRouter时候传入的routes路由配置来生成这个内部路由匹配器相关的信息。

// vuejs:router/packages/router/src/router.ts

export function createRouter(options: RouterOptions): Router {
  const matcher = createRouterMatcher(options.routes, options)

  function addRoute(
    parentOrRoute: RouteRecordName | RouteRecordRaw,
    route?: RouteRecordRaw
  ) {
    let parent: Parameters<(typeof matcher)['addRoute']>[1] | undefined
    let record: RouteRecordRaw
    if (isRouteName(parentOrRoute)) {
      parent = matcher.getRecordMatcher(parentOrRoute)
      record = route!
    } else {
      record = parentOrRoute
    }

    return matcher.addRoute(record, parent)
  }

  function removeRoute(name: RouteRecordName) {
    const recordMatcher = matcher.getRecordMatcher(name)
    if (recordMatcher) {
      matcher.removeRoute(recordMatcher)
    } else if (__DEV__) {
      warn(`Cannot remove non-existent route "${String(name)}"`)
    }
  }

  function getRoutes() {
    return matcher.getRoutes().map(routeMatcher => routeMatcher.record)
  }

  function hasRoute(name: RouteRecordName): boolean {
    return !!matcher.getRecordMatcher(name)
  }

  const router: Router = {
    addRoute,
    removeRoute,
    hasRoute,
    getRoutes,
    // ··· ···
  }

  return router
}

在 VueRouter 4 中路由匹配器所维护路由配置的主要作用如下:

  1. 将路由路径转换为路由对象:当VueRouter接收到一个路由路径时,matcher会将其转换为一个包含路由信息的对象。这个对象包括路由路径、参数、查询字符串等信息。
  2. 进行路由匹配:matcher将路由对象与路由表进行匹配,找到与之匹配的路由。如果找不到匹配的路由,matcher会尝试使用“404 Not Found”路由来处理未匹配的路径。
  3. 处理动态路由:matcher支持动态路由,可以在运行时动态添加或删除路由。当动态路由发生变化时,matcher会更新路由表并重新进行路由匹配。
  4. 处理路由路径的规范化:matcher会将路由路径进行规范化,以确保路由路径的一致性。例如,将重复的斜杠去除、将路径转换为小写字母等。

3.2 导航跳转时候进行判断路由变化

extractChangingRecords

参数:to、from

返回:二维数组,该数组包含三个对象 - 需要跳转离开的路由记录 leavingRecords、更新的路由记录 updatingRecords、即将进入激活的路由记录 enteringRecords。

在 Vue Router 中,extractChangingRecords函数通常用于在路由导航过程中确定需要执行的路由钩子函数。

例如,在路由跳转前,需要执行当前路由记录中的 beforeRouteLeave 钩子函数以及进入的路由记录中的 beforeRouteEnter 钩子函数;在路由更新时,需要执行更新的路由记录中的 beforeRouteUpdate 钩子函数。通过使用extractChangingRecords函数,可以方便地定位到相关的路由记录进而确定需要执行的钩子函数,提高路由系统的效率和灵活性。

// vuejs:router/packages/router/src/router.ts

function extractChangingRecords(
  to: RouteLocationNormalized,
  from: RouteLocationNormalizedLoaded
) {
  const leavingRecords: RouteRecordNormalized[] = []   // 跳转离开的路由列表
  const updatingRecords: RouteRecordNormalized[] = []  // 跳转需要更新的路由列表
  const enteringRecords: RouteRecordNormalized[] = []  // 跳转进入的路由列表

  // 比较 当前路由 from 与 目标路由 to 的路由匹配记录数量,取较大值
  const len = Math.max(from.matched.length, to.matched.length)

  // 遍历前面所获取到的较大值
  for (let i = 0; i < len; i++) {
    const recordFrom = from.matched[i] // 从当前路由当中取出一个路由匹配记录
    
    if (recordFrom) {
      // 判断这个当前路由的路由匹配记录能否在目标路由当中找到对应记录
      if (to.matched.find(record => isSameRouteRecord(record, recordFrom)))
        updatingRecords.push(recordFrom) // 找到了则证明需要更新
      else leavingRecords.push(recordFrom) // 没找到则证明需要离开
    }
    
    const recordTo = to.matched[i] // 从当前目标路由当中取出一个路由匹配记录
    
    if (recordTo) {
      // 判断这个目标路由的路由匹配记录能否在当前路由当中找到对应记录
      if (!from.matched.find(record => isSameRouteRecord(record, recordTo))) {
        enteringRecords.push(recordTo) // 若找不到则证明需要进行跳转进入
      }
    }
  }

  // 按顺序组装成一个数组进行返回
  return [leavingRecords, updatingRecords, enteringRecords]
}

首先,它创建了三个空数组,分别用于存放离开的路由记录、更新的路由记录和进入的路由记录。

然后,它通过遍历遍历了当前路由 from 与 目标路由 to 的路由匹配记录两个数组的长度的较大值,比较这两个路由对象它们的路由匹配记录是否相同,以确定哪些路由记录发生了变化。

  • 对于当前路由的每一个路由记录,如果在目标路由中能找到相同的记录,就将它添加到 updatingRecords 数组中,否则添加到 leavingRecords 数组中;
  • 对于目标路由的每一个路由记录,如果在当前路由中找不到相同的记录,就将它添加到 enteringRecords 数组中;

最后,函数返回一个包含三个数组的元组,分别表示离开的路由记录、更新的路由记录和进入的路由记录。





章节总结

这个章节的内容相对来讲其实是相对来讲有点抽象、比较难以理解,但是也是 VueRouter 这个前端路由库能够正确、高性能衔接运行起来必不可少的一个大功能根基模块。

  • 首先是路由配置的一个内部维护是基于路由匹配器实现,导航跳转都是依赖此来命中找寻对应的配置视图组件进行页面渲染;
    • 并且在全局的 VueRouter 对象当中挂载了相关操作路由配置的方法来实现动态的对路由配置进行修改操作。
  • 在导航守卫当中也是根据路由匹配器来找寻该次导航跳转所涉及的相关路由记录,后续将这些获取到的相关路由匹配记录再进行提取加工获取到对应路由的一些导航守卫钩子回调来执行触发订阅。





参考资料

相关的系列文章

相关的参考资料