【vue-router源码】六、router.resolve源码解析

【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源码分析
  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官网学习下。

该篇文章将介绍router.resolve的实现。

使用

router.resolve方法返回路由地址的标准化版本。

router.resolve('admin')
router.resolve({ path: '/admin' })

resolve

resolve接收两个参数:rawLocationcurrentLocation(可选)。其中rawLocation是待转换的路由,rawLocation可以是个对象也可以是个字符串。currentLocation不传默认是currentRoute

resolve中有是两个分支:

  • 如果rawLocationstring类型
    调用parseURL解析rawLocation:
const locationNormalized = parseURL(
  parseQuery,
  rawLocation,
  currentLocation.path
)

parseURL接收三个参数:parseQuery(一个query解析函数)、location(被解析的location)、currentLocation(当前的location)。

export function parseURL(
  parseQuery: (search: string) => LocationQuery,
  location: string,
  currentLocation: string = '/'
): LocationNormalized {
  let path: string | undefined,
    query: LocationQuery = {},
    searchString = '',
    hash = ''

  // location中?的位置
  const searchPos = location.indexOf('?')
  // location中#的位置,如果location中有?,在?之后找#
  const hashPos = location.indexOf('#', searchPos > -1 ? searchPos : 0)

  // 如果
  if (searchPos > -1) {
    // 从location中截取[0, searchPos)位置的字符串作为path
    path = location.slice(0, searchPos)
    // 从location截取含search的字符串,不包含hash部分
    searchString = location.slice(
      searchPos + 1,
      hashPos > -1 ? hashPos : location.length
    )
    // 调用parseQuery生成query对象
    query = parseQuery(searchString)
  }
  // 如果location中有hash
  if (hashPos > -1) {
    path = path || location.slice(0, hashPos)
    // 从location中截取[hashPos, location.length)作为hash(包含#)
    hash = location.slice(hashPos, location.length)
  }

  // 解析以.开头的相对路径
  path = resolveRelativePath(path != null ? path : location, currentLocation)
  // empty path means a relative query or hash `?foo=f`, `#thing`

  return {
  	// fullPath = path + searchString + hash
    fullPath: path + (searchString && '?') + searchString + hash,
    path,
    query,
    hash,
  }
}

来看下,相对路径的解析过程:

export function resolveRelativePath(to: string, from: string): string {
  // 如果to以/开头,说明是个绝对路径,直接返回即可
  if (to.startsWith('/')) return to
  // 如果from不是以/开头,那么说明from不是绝对路径,也就无法推测出to的绝对路径,此时直接返回to
  if (__DEV__ && !from.startsWith('/')) {
    warn(
      `Cannot resolve a relative location without an absolute path. Trying to resolve "${to}" from "${from}". It should look like "/${from}".`
    )
    return to
  }

  if (!to) return from
  // 使用/分割from与to
  const fromSegments = from.split('/')
  const toSegments = to.split('/')

  // 初始化position默认为fromSegments的最后一个索引
  let position = fromSegments.length - 1
  let toPosition: number
  let segment: string

  for (toPosition = 0; toPosition < toSegments.length; toPosition++) {
    segment = toSegments[toPosition]
    // 保证position不会小于0
    if (position === 1 || segment === '.') continue
    if (segment === '..') position--
    else break
  }

  return (
    fromSegments.slice(0, position).join('/') +
    '/' +
    toSegments
      .slice(toPosition - (toPosition === toSegments.length ? 1 : 0))
      .join('/')
  )
}

to=ccfrom=/aa/bb,经过resolveRelativePath后:/aa/cc
to=ccfrom=/aa/bb/,经过resolveRelativePath后:/aa/bb/cc
to=./ccfrom=/aa/bb,经过resolveRelativePath后:/aa/cc
to=./ccfrom=/aa/bb/,经过resolveRelativePath后:/aa/bb/cc
to=../ccfrom=/aa/bb,经过resolveRelativePath后:/aa
to=../ccfrom=/aa/bb/,经过resolveRelativePath后:/aa/cc
如果from/to=ccto=./ccto=../ccto=../../ccto=./../ccto=.././cc经过resolveRelativePath始终返回/cc

回到resolve中,解析完rawLocation后,调用matcher.resolve

const matchedRoute = matcher.resolve(
  { path: locationNormalized.path },
  currentLocation
)
// 使用routerHistory.createHref创建href
const href = routerHistory.createHref(locationNormalized.fullPath)

最后返回对象:

return assign(locationNormalized, matchedRoute, {
  // 对params中的value进行decodeURIComponent
  params:decodeParams(matchedRoute.params),
  // 对hash进行decodeURIComponent
  hash: decode(locationNormalized.hash),
  redirectedFrom: undefined,
  href,
})
  • rawLocation不是string类型
let matcherLocation: MatcherLocationRaw

// 如果rawLocation中有path属性
if ('path' in rawLocation) {
  // rawLocation中的params会被忽略
  if (
    __DEV__ &&
    'params' in rawLocation &&
    !('name' in rawLocation) &&
    Object.keys(rawLocation.params).length
  ) {
    warn(
      `Path "${
        rawLocation.path
      }" was passed with params but they will be ignored. Use a named route alongside params instead.`
    )
  }
  // 处理path为绝对路径
  matcherLocation = assign({}, rawLocation, {
    path: parseURL(parseQuery, rawLocation.path, currentLocation.path).path,
  })
} else {
  // 删除空的参数
  const targetParams = assign({}, rawLocation.params)
  for (const key in targetParams) {
    if (targetParams[key] == null) {
      delete targetParams[key]
    }
  }
  // 对params进行编码
  matcherLocation = assign({}, rawLocation, {
    params: encodeParams(rawLocation.params),
  })
  // 将当前位置的params编码 当前位置的参数被解码,我们需要对它们进行编码以防匹配器合并参数
  currentLocation.params = encodeParams(currentLocation.params)
}

// 调用matcher.resolve获取路由相关信息
const matchedRoute = matcher.resolve(matcherLocation, currentLocation)
const hash = rawLocation.hash || ''

if (__DEV__ && hash && !hash.startsWith('#')) {
  warn(
    `A \`hash\` should always start with the character "#". Replace "${hash}" with "#${hash}".`
  )
}

// 由于matcher已经合并了当前位置的参数,所以需要进行解码
matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params))

// 生成完整path
const fullPath = stringifyURL(
  stringifyQuery,
  assign({}, rawLocation, {
    hash: encodeHash(hash),
    path: matchedRoute.path,
  })
)
// routerHistory.createHref会删除#之前的任意字符
const href = routerHistory.createHref(fullPath)
if (__DEV__) {
  if (href.startsWith('//')) {
    warn(
      `Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`
    )
  } else if (!matchedRoute.matched.length) {
    warn(
      `No match found for location with path "${
        'path' in rawLocation ? rawLocation.path : rawLocation
      }"`
    )
  }
}

return assign(
  {
    fullPath,
    hash,
    query:
    // 如果query是个嵌套对象,normalizeQuery会将嵌套的对象toString,如果用户使用qs等库,我们需要保持query的状态
    // https://github.com/vuejs/router/issues/328#issuecomment-649481567
      stringifyQuery === originalStringifyQuery
        ? normalizeQuery(rawLocation.query)
        : ((rawLocation.query || {}) as LocationQuery),
  },
  matchedRoute,
  {
    redirectedFrom: undefined,
    href,
  }
)
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

MAXLZ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值