【vue-router源码】三、理解Vue-router中的Matcher

本文深入解析Vue-Router源码中的Matcher实现,包括Matcher的创建、路由的添加、删除、解析等核心功能。Matcher是路由匹配的核心,通过tokenizePath将path转换为token数组,再通过tokensToParser构建解析器,实现路径正则、动态参数等功能。文章详细介绍了addRoute、removeRoute、resolve等方法,阐述了Matcher在路由管理中的作用和实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【vue-rouer源码】系列文章

  1. 【vue-router源码】一、router.install解析
  2. 【vue-router源码】二、createWebHistory、createWebHashHistory、createMemoryHistory源码解析
  3. 【vue-router源码】三、理解Vue-router中的Matcher
  4. 【vue-router源码】四、createRouter源码解析
  5. 【vue-router源码】五、router.addRoute、router.removeRoute、router.hasRoute、router.getRoutes源码分析
  6. 【vue-router源码】六、router.resolve源码解析
  7. 【vue-router源码】七、router.push、router.replace源码解析
  8. 【vue-router源码】八、router.go、router.back、router.forward源码解析
  9. 【vue-router源码】九、全局导航守卫的实现
  10. 【vue-router源码】十、isReady源码解析
  11. 【vue-router源码】十一、onBeforeRouteLeave、onBeforeRouteUpdate源码分析
  12. 【vue-router源码】十二、useRoute、useRouter、useLink源码分析
  13. 【vue-router源码】十三、RouterLink源码分析
  14. 【vue-router源码】十四、RouterView源码分析


前言

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

该篇文章将带你理解vue-routermatcher的实现。

matcher初识

在开始介绍matcher实现之前,我们先了解下matcher是什么?它的作用是什么?
vue-router中,每一个我们定义的路由都会被解析成一个对应的matcherRouteRecordMatcher类型),路由的增删改查都会依靠matcher来实现。

createRouterMatcher

createRouter中会通过createRouterMatcher创建一个matcherRouterMatcher类型)。

export function createRouterMatcher(
  routes: 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) {
    // ... }

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

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

  function getRoutes() {
    // ... }

  function insertMatcher(matcher: RouteRecordMatcher) {
    // ... }

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

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

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

createRouterMatcher接收两个参数:routesglobalOptions。其中routes为我们定义的路由表,也就是在createRouter时传入的options.routes,而globalOptions就是createRouter中的options
createRouterMatcher中声明了两个变量matchersmatcherMap,用来存储通过路由表解析的matcherRouteRecordMatcher类型),然后遍历routes,对每个元素调用addRoute方法。最后返回一个对象,该对象有addRouteresolveremoveRoutegetRoutegetRecordMatcher几个属性,这几个属性都对应着一个函数。
接下来我们看下这几个函数:

addRoute

addRoute函数接收三个参数:record(新增的路由)、parent(父matcher)、originalRecord(原始matcher)。

function addRoute(
  record: RouteRecordRaw,
  parent?: RouteRecordMatcher,
  originalRecord?: RouteRecordMatcher
) {
   
  // used later on to remove by name
  const isRootAdd = !originalRecord
  // 标准化化路由记录
  const mainNormalizedRecord = normalizeRouteRecord(record)
  // aliasOf表示此记录是否是另一个记录的别名
  mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record
  const options: PathParserOptions = mergeOptions(globalOptions, record)
  // 声明一个记录的数组用来处理别名
  const normalizedRecords: typeof mainNormalizedRecord[] = [
    mainNormalizedRecord,
  ]
  // 如果record设置了别名
  if ('alias' in record) {
   
    // 别名数组
    const aliases =
      typeof record.alias === 'string' ? [record.alias] : record.alias!
    // 遍历别名数组,并根据别名创建记录存储到normalizedRecords中
    for (const alias of aliases) {
   
      normalizedRecords.push(
        assign({
   }, mainNormalizedRecord, {
   
          components: originalRecord
            ? originalRecord.record.components
            : mainNormalizedRecord.components,
          path: alias,
          // 如果有原始记录,aliasOf为原始记录,如果没有原始记录就是它自己
          aliasOf: originalRecord
            ? originalRecord.record
            : mainNormalizedRecord,
        }) as typeof mainNormalizedRecord
      )
    }
  }

  let matcher: RouteRecordMatcher
  let originalMatcher: RouteRecordMatcher | undefined

  // 遍历normalizedRecords
  for (const normalizedRecord of normalizedRecords) {
   
    
    // 处理normalizedRecord.path为完整的path
    const {
    path } = normalizedRecord
    // 如果path不是以/开头,那么说明它不是根路由,需要拼接为完整的path
    // { path: '/a', children: [ { path: 'b' } ] } -> { path: '/a', children: [ { path: '/a/b' } ] }
    if (parent && path[0] !== '/') {
   
      const parentPath = parent.record.path
      const connectingSlash =
        parentPath[parentPath.length - 1] === '/' ? '' : '/'
      normalizedRecord.path =
        parent.record.path + (path && connectingSlash + path)
    }

    // 提示*应使用正则表示式形式
    if (__DEV__ && normalizedRecord.path === '*') {
   
      throw new Error(
        'Catch all routes ("*") must now be defined using a param with a custom regexp.\n' +
          'See more at https://next.router.vuejs.org/guide/migration/#removed-star-or-catch-all-routes.'
      )
    }

    // 创建一个路由记录匹配器
    matcher = createRouteRecordMatcher(normalizedRecord, parent, options)

    // 检查是否有丢失的参数
    if (__DEV__ && parent && path[0] === '/')
      checkMissingParamsInAbsolutePath(matcher, parent)

    // 如果有originalRecord,将matcher放入原始记录的alias中,以便后续能够删除
    if (originalRecord) {
   
      originalRecord.alias.push(matcher)
      // 检查originalRecord与matcher中动态参数是否相同
      if (__DEV__) {
   
        checkSameParams(originalRecord, matcher)
      }
    } else {
    // 没有originalRecord
      // 因为原始记录索引为0,所以originalMatcher为有原始记录所产生的matcher
      originalMatcher = originalMatcher || matcher
      // 如果matcher不是原始记录产生的matcher,说明此时matcher是由别名记录产生的,此时将matcher放入originalMatcher.alias中
      if (originalMatcher !== matcher) originalMatcher.alias.push(matcher)
      // 如果命名并且仅用于顶部记录,则删除路由(避免嵌套调用)
      if (isRootAdd && record.name && !isAliasRecord(matcher))
        removeRoute(record.name)
    }

    // 遍历children,递归addRoute
    if ('children' in mainNormalizedRecord) {
   
      const children = mainNormalizedRecord.children
      for (let i = 0; i < children.length; i++) {
   
        addRoute(
          children[i],
          matcher,
          originalRecord && originalRecord.children[i]
        )
      }
    }

    originalRecord = originalRecord || matcher
    // 添加matcher
    insertMatcher(matcher)
  }

  // 返回一个删除原始matcher的方法
  return originalMatcher
    ? () => {
   
        removeRoute(originalMatcher!)
      }
    : noop
}

addRoute中,会对record进行标准化处理(normalizeRouteRecord),如果存在原始的matcher,也就是originalRecord,说明此时要添加的路由是另一记录的别名,这时会将originalRecord.record存入mainNormalizedRecord.aliasOf中。

const isRootAdd = !originalRecord
// 标准化化路由记录
const mainNormalizedRecord = normalizeRouteRecord(record)
// aliasOf表示此记录是否是另一个记录的别名
mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record
const options: PathParserOptions = mergeOptions(globalOptions, record)
// 声明一个记录的数组用来处理别名
const normalizedRecords: typeof mainNormalizedRecord[] = [
  mainNormalizedRecord,
]

然后会遍历record的别名,向normalizedRecords中添加由别名产生的路由:

if ('alias' in record) {
   
  // 别名数组
  const aliases =
    typeof record.alias === 'string' ? [record.alias] : record.alias!
  // 遍历别名数组,并根据别名创建记录存储到normalizedRecords中
  for (const alias of aliases) {
   
    normalizedRecords.push(
      assign({
   }, mainNormalizedRecord, {
   
        components: originalRecord
          ? originalRecord.record.components
          : mainNormalizedRecord.components,
        path: alias,
        // 如果有原始记录,aliasOf为原始记录,如果没有原始记录就是它自己
        aliasOf: originalRecord
          ? originalRecord.record
          : mainNormalizedRecord,
      }) as typeof mainNormalizedRecord
    )
  }
}

紧接着会遍历normalizedRecords:在这个遍历过程中,会首先将path处理成完整的path,然后通过createRouteRecordMatcher方法创建一个matcherRouteRecordMatcher类型),如果matcher是由别名产生的,那么matcher会被加入由原始记录产生的matcher中的alias属性中。然后会遍历mainNormalizedRecordchildren属性,递归调用addRoute方法。在最后,调用insertMatcher添加新创建的matcher

for (const normalizedRecord of normalizedRecords) {
   
  
  // 处理normalizedRecord.path为完整的path
  const {
    path } = normalizedRecord
  // 如果path不是以/开头,那么说明它不是根路由,需要拼接为完整的path
  // { path: '/a', children: [ { path: 'b' } ] } -> { path: '/a', children: [ { path: '/a/b' } ] }
  if (parent && path[0] !== '/') {
   
    const parentPath = parent.record.path
    const connectingSlash =
      parentPath[parentPath.length - 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MAXLZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值