【vue-rouer源码】系列文章
- 【vue-router源码】一、router.install解析
- 【vue-router源码】二、createWebHistory、createWebHashHistory、createMemoryHistory源码解析
- 【vue-router源码】三、理解Vue-router中的Matcher
- 【vue-router源码】四、createRouter源码解析
- 【vue-router源码】五、router.addRoute、router.removeRoute、router.hasRoute、router.getRoutes源码分析
- 【vue-router源码】六、router.resolve源码解析
- 【vue-router源码】七、router.push、router.replace源码解析
- 【vue-router源码】八、router.go、router.back、router.forward源码解析
- 【vue-router源码】九、全局导航守卫的实现
- 【vue-router源码】十、isReady源码解析
- 【vue-router源码】十一、onBeforeRouteLeave、onBeforeRouteUpdate源码分析
- 【vue-router源码】十二、useRoute、useRouter、useLink源码分析
- 【vue-router源码】十三、RouterLink源码分析
- 【vue-router源码】十四、RouterView源码分析
目录
前言
【vue-router源码】系列文章将带你从0开始了解vue-router
的具体实现。该系列文章源码参考vue-router v4.0.15
。
源码地址:https://github.com/vuejs/router
阅读该文章的前提是你最好了解vue-router
的基本使用,如果你没有使用过的话,可通过vue-router官网学习下。
该篇文章将带你理解vue-router
中matcher
的实现。
matcher初识
在开始介绍matcher
实现之前,我们先了解下matcher
是什么?它的作用是什么?
在vue-router
中,每一个我们定义的路由都会被解析成一个对应的matcher
(RouteRecordMatcher
类型),路由的增删改查都会依靠matcher
来实现。
createRouterMatcher
在createRouter
中会通过createRouterMatcher
创建一个matcher
(RouterMatcher
类型)。
export function createRouterMatcher(
routes: RouteRecordRaw[],
globalOptions: PathParserOptions
): RouterMatcher {
const matchers: RouteRecordMatcher[] = []
const matcherMap = new Map<RouteRecordName, RouteRecordMatcher>()
globalOptions = mergeOptions(
{
strict: false, end: true, sensitive: false } as PathParserOptions,
globalOptions
)
function getRecordMatcher(name: RouteRecordName) {
// ... }
function addRoute(
record: RouteRecordRaw,
parent?: RouteRecordMatcher,
originalRecord?: RouteRecordMatcher
) {
// ...
}
function removeRoute(matcherRef: RouteRecordName | RouteRecordMatcher) {
// ... }
function getRoutes() {
// ... }
function insertMatcher(matcher: RouteRecordMatcher) {
// ... }
function resolve(
location: Readonly<MatcherLocationRaw>,
currentLocation: Readonly<MatcherLocation>
): MatcherLocation {
// ...
}
routes.forEach(route => addRoute(route))
return {
addRoute, resolve, removeRoute, getRoutes, getRecordMatcher }
}
createRouterMatcher
接收两个参数:routes
、globalOptions
。其中routes
为我们定义的路由表,也就是在createRouter
时传入的options.routes
,而globalOptions
就是createRouter
中的options
。
createRouterMatcher
中声明了两个变量matchers
、matcherMap
,用来存储通过路由表解析的matcher
(RouteRecordMatcher
类型),然后遍历routes
,对每个元素调用addRoute
方法。最后返回一个对象,该对象有addRoute
、resolve
、removeRoute
、getRoute
、getRecordMatcher
几个属性,这几个属性都对应着一个函数。
接下来我们看下这几个函数:
addRoute
addRoute
函数接收三个参数:record
(新增的路由)、parent
(父matcher
)、originalRecord
(原始matcher
)。
function addRoute(
record: RouteRecordRaw,
parent?: RouteRecordMatcher,
originalRecord?: RouteRecordMatcher
) {
// used later on to remove by name
const isRootAdd = !originalRecord
// 标准化化路由记录
const mainNormalizedRecord = normalizeRouteRecord(record)
// aliasOf表示此记录是否是另一个记录的别名
mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record
const options: PathParserOptions = mergeOptions(globalOptions, record)
// 声明一个记录的数组用来处理别名
const normalizedRecords: typeof mainNormalizedRecord[] = [
mainNormalizedRecord,
]
// 如果record设置了别名
if ('alias' in record) {
// 别名数组
const aliases =
typeof record.alias === 'string' ? [record.alias] : record.alias!
// 遍历别名数组,并根据别名创建记录存储到normalizedRecords中
for (const alias of aliases) {
normalizedRecords.push(
assign({
}, mainNormalizedRecord, {
components: originalRecord
? originalRecord.record.components
: mainNormalizedRecord.components,
path: alias,
// 如果有原始记录,aliasOf为原始记录,如果没有原始记录就是它自己
aliasOf: originalRecord
? originalRecord.record
: mainNormalizedRecord,
}) as typeof mainNormalizedRecord
)
}
}
let matcher: RouteRecordMatcher
let originalMatcher: RouteRecordMatcher | undefined
// 遍历normalizedRecords
for (const normalizedRecord of normalizedRecords) {
// 处理normalizedRecord.path为完整的path
const {
path } = normalizedRecord
// 如果path不是以/开头,那么说明它不是根路由,需要拼接为完整的path
// { path: '/a', children: [ { path: 'b' } ] } -> { path: '/a', children: [ { path: '/a/b' } ] }
if (parent && path[0] !== '/') {
const parentPath = parent.record.path
const connectingSlash =
parentPath[parentPath.length - 1] === '/' ? '' : '/'
normalizedRecord.path =
parent.record.path + (path && connectingSlash + path)
}
// 提示*应使用正则表示式形式
if (__DEV__ && normalizedRecord.path === '*') {
throw new Error(
'Catch all routes ("*") must now be defined using a param with a custom regexp.\n' +
'See more at https://next.router.vuejs.org/guide/migration/#removed-star-or-catch-all-routes.'
)
}
// 创建一个路由记录匹配器
matcher = createRouteRecordMatcher(normalizedRecord, parent, options)
// 检查是否有丢失的参数
if (__DEV__ && parent && path[0] === '/')
checkMissingParamsInAbsolutePath(matcher, parent)
// 如果有originalRecord,将matcher放入原始记录的alias中,以便后续能够删除
if (originalRecord) {
originalRecord.alias.push(matcher)
// 检查originalRecord与matcher中动态参数是否相同
if (__DEV__) {
checkSameParams(originalRecord, matcher)
}
} else {
// 没有originalRecord
// 因为原始记录索引为0,所以originalMatcher为有原始记录所产生的matcher
originalMatcher = originalMatcher || matcher
// 如果matcher不是原始记录产生的matcher,说明此时matcher是由别名记录产生的,此时将matcher放入originalMatcher.alias中
if (originalMatcher !== matcher) originalMatcher.alias.push(matcher)
// 如果命名并且仅用于顶部记录,则删除路由(避免嵌套调用)
if (isRootAdd && record.name && !isAliasRecord(matcher))
removeRoute(record.name)
}
// 遍历children,递归addRoute
if ('children' in mainNormalizedRecord) {
const children = mainNormalizedRecord.children
for (let i = 0; i < children.length; i++) {
addRoute(
children[i],
matcher,
originalRecord && originalRecord.children[i]
)
}
}
originalRecord = originalRecord || matcher
// 添加matcher
insertMatcher(matcher)
}
// 返回一个删除原始matcher的方法
return originalMatcher
? () => {
removeRoute(originalMatcher!)
}
: noop
}
在addRoute
中,会对record
进行标准化处理(normalizeRouteRecord
),如果存在原始的matcher
,也就是originalRecord
,说明此时要添加的路由是另一记录的别名,这时会将originalRecord.record
存入mainNormalizedRecord.aliasOf
中。
const isRootAdd = !originalRecord
// 标准化化路由记录
const mainNormalizedRecord = normalizeRouteRecord(record)
// aliasOf表示此记录是否是另一个记录的别名
mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record
const options: PathParserOptions = mergeOptions(globalOptions, record)
// 声明一个记录的数组用来处理别名
const normalizedRecords: typeof mainNormalizedRecord[] = [
mainNormalizedRecord,
]
然后会遍历record
的别名,向normalizedRecords
中添加由别名产生的路由:
if ('alias' in record) {
// 别名数组
const aliases =
typeof record.alias === 'string' ? [record.alias] : record.alias!
// 遍历别名数组,并根据别名创建记录存储到normalizedRecords中
for (const alias of aliases) {
normalizedRecords.push(
assign({
}, mainNormalizedRecord, {
components: originalRecord
? originalRecord.record.components
: mainNormalizedRecord.components,
path: alias,
// 如果有原始记录,aliasOf为原始记录,如果没有原始记录就是它自己
aliasOf: originalRecord
? originalRecord.record
: mainNormalizedRecord,
}) as typeof mainNormalizedRecord
)
}
}
紧接着会遍历normalizedRecords
:在这个遍历过程中,会首先将path
处理成完整的path
,然后通过createRouteRecordMatcher
方法创建一个matcher
(RouteRecordMatcher
类型),如果matcher
是由别名产生的,那么matcher
会被加入由原始记录产生的matcher
中的alias
属性中。然后会遍历mainNormalizedRecord
的children
属性,递归调用addRoute
方法。在最后,调用insertMatcher
添加新创建的matcher
。
for (const normalizedRecord of normalizedRecords) {
// 处理normalizedRecord.path为完整的path
const {
path } = normalizedRecord
// 如果path不是以/开头,那么说明它不是根路由,需要拼接为完整的path
// { path: '/a', children: [ { path: 'b' } ] } -> { path: '/a', children: [ { path: '/a/b' } ] }
if (parent && path[0] !== '/') {
const parentPath = parent.record.path
const connectingSlash =
parentPath[parentPath.length - 1