前言
【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中有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=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创立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, })