前言
【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,
}
)
发表回复