前言

【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中有hashif (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的绝对路径,此时间接返回toif (__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与toconst fromSegments = from.split('/')const toSegments = to.split('/')// 初始化position默认为fromSegments的最初一个索引let position = fromSegments.length - 1let toPosition: numberlet segment: stringfor (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创立hrefconst 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))// 生成残缺pathconst 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,  })