乐趣区

关于前端:vuerouter源码六routerresolve源码解析

前言

【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,
  }
)
退出移动版