【vue-router源码】六、router.resolve源码解析
前言
【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
接收两个参数:rawLocation
、currentLocation
(可选)。其中rawLocation
是待转换的路由,rawLocation
可以是个对象也可以是个字符串。currentLocation
不传默认是currentRoute
。
在resolve
中有是两个分支:
如果
rawLocation
是string
类型调用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=cc
,from=/aa/bb
,经过resolveRelativePath
后:/aa/cc
to=cc
,from=/aa/bb/
,经过resolveRelativePath
后:/aa/bb/cc
to=./cc
,from=/aa/bb
,经过resolveRelativePath
后:/aa/cc
to=./cc
,from=/aa/bb/
,经过resolveRelativePath
后:/aa/bb/cc
to=../cc
,from=/aa/bb
,经过resolveRelativePath
后:/aa
to=../cc
,from=/aa/bb/
,经过resolveRelativePath
后:/aa/cc
如果from/
,to=cc
、to=./cc
、to=../cc
、to=../../cc
、to=./../cc
、to=.././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,
}
)
转载自:https://segmentfault.com/a/1190000041954090