【vue-rouer源码】系列文章
- 【vue-router源码】四、createRouter源码解析
目录
前言
【vue-router源码】电商商城定制开发系列文章将带你从0开始了解vue-router
电商商城定制开发的具体实现。电商商城定制开发该系列文章源码参考vue-router v4.0.15
。
源码地址:
电商商城定制开发阅读该文章的前提是你最好了解vue-router
电商商城定制开发的基本使用,如果你没有使用过的话,可通过学习下。
该篇文章将带你分析createRouter
的实现。
使用
const routerHistory = createWebHistory()export const router = createRouter({ history: routerHistory, strict: true, routes: [ { path: '/home', redirect: '/' }, { path: '/', components: { default: Home, other: component }, props: { default: to => ({ waited: to.meta.waitedFor }) }, }, { path: '/nested', alias: '/anidado', component: Nested, name: 'Nested', children: [ { path: 'nested', alias: 'a', name: 'NestedNested', component: Nested, children: [ { name: 'NestedNestedNested', path: 'nested', component: Nested, }, ], }, { path: 'other', alias: 'otherAlias', component: Nested, name: 'NestedOther', }, { path: 'also-as-absolute', alias: '/absolute', name: 'absolute-child', component: Nested, }, ], }, ], async scrollBehavior(to, from, savedPosition) { await scrollWaiter.wait() if (savedPosition) { return savedPosition } else { if (to.matched.every((record, i) => from.matched[i] !== record)) return { left: 0, top: 0 } } return false },})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
createRouter
在分析createRouter
之前,先来看下它的参数类型:
export interface _PathParserOptions { // 使用正则时区分大小写,默认false sensitive?: boolean // 是否禁止尾随斜杠,默认false strict?: boolean // 正则表达式前应该加^,默认true start?: boolean // 正则表达式以$结尾,默认为true end?: boolean}export type PathParserOptions = Pick< _PathParserOptions, 'end' | 'sensitive' | 'strict'>export interface RouterOptions extends PathParserOptions { history: RouterHistory // 路由表 routes: RouteRecordRaw[] // 在页面之间导航时控制滚动行为。可以返回一个 Promise 来延迟滚动。 scrollBehavior?: RouterScrollBehavior // 用于自定义如何解析query parseQuery?: typeof originalParseQuery // 用于自定义查询对象如何转为字符串 stringifyQuery?: typeof originalStringifyQuery // 激活RouterLink的默认类 linkActiveClass?: string // 精准激活RouterLink的默认类 linkExactActiveClass?: string}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
我们来看下createRouter
具体做了什么。createRouter
方法共885(包含空行)行,乍一看可能会觉得方法很复杂,仔细观察,其实很大一部分代码都是声明一些函数。我们可以先暂时抛开这些函数声明看其余部分。
首先会使用createRouterMatcher
方法创建了一个路由匹配器matcher
,从options
中提取parseQuery
、stringifyQuery
、history
属性,如果options
中没有history
,抛出错误。
const matcher = createRouterMatcher(options.routes, options)const parseQuery = options.parseQuery || originalParseQueryconst stringifyQuery = options.stringifyQuery || originalStringifyQueryconst routerHistory = options.historyif (__DEV__ && !routerHistory) throw new Error( 'Provide the "history" option when calling "createRouter()":' + ' https://next.router.vuejs.org/api/#history.' )
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
紧接着声明了一些全局守卫相关的变量,和一些关于params
的处理方法,其中有关全局守卫的变量都是通过useCallbacks
创建的,params
相关方法通过applyToParams
创建。
// 全局前置守卫相关方法const beforeGuards = useCallbacks<NavigationGuardWithThis<undefined>>()// 全局解析守卫相关方法const beforeResolveGuards = useCallbacks<NavigationGuardWithThis<undefined>>()// 全局后置钩子方法const afterGuards = useCallbacks<NavigationHookAfter>()// 当前路由,浅层响应式对象const currentRoute = shallowRef<RouteLocationNormalizedLoaded>( START_LOCATION_NORMALIZED)let pendingLocation: RouteLocation = START_LOCATION_NORMALIZED// 如果浏览器环境下设置了scrollBehavior,那么需要防止页面自动恢复页面位置// https://developer.mozilla.org/zh-CN/docs/Web/API/History/scrollRestorationif (isBrowser && options.scrollBehavior && 'scrollRestoration' in history) { history.scrollRestoration = 'manual'}// 标准化params,转字符串const normalizeParams = applyToParams.bind( null, paramValue => '' + paramValue)// 编码paramconst encodeParams = applyToParams.bind(null, encodeParam)// 解码paramsconst decodeParams: (params: RouteParams | undefined) => RouteParams = applyToParams.bind(null, decode)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
关于useCallbacks
的实现:在useCallbacks
中声明一个handlers
数组用来保存所有添加的方法,useCallbacks
的返回值中包括三个方法:add
(添加一个handler
,并返回一个删除handler
的函数)、list
(返回所有handler
)、reset
(清空所有handler
)
export function useCallbacks<T>() { let handlers: T[] = [] function add(handler: T): () => void { handlers.push(handler) return () => { const i = handlers.indexOf(handler) if (i > -1) handlers.splice(i, 1) } } function reset() { handlers = [] } return { add, list: () => handlers, reset, }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
applyToParams
的实现:接收一个处理函数和params
对象,遍历params
对象,并对每一个属性值执行fn
并将结果赋给一个新的对象。
export function applyToParams( fn: (v: string | number | null | undefined) => string, params: RouteParamsRaw | undefined): RouteParams { const newParams: RouteParams = {} for (const key in params) { const value = params[key] newParams[key] = Array.isArray(value) ? value.map(fn) : fn(value) } return newParams}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
然后声明了大量的函数,包括addRoute
、removeRoute
、getRoutes
等,这些函数也就是我们日常使用的addRoute
、removeRoute
等。
在createRouter
的最后创建了一个router
对象,并将其返回,该对象几乎包含了声明的所有函数。
总结
createRouter
函数中声明了一些全局钩子所需的变量和很多函数,这些函数就是我们日常使用的一些方法,如addRoute
、removeRoute
等,在函数的最后,声明了一个router
对象,前面所声明的函数多数都会被包含在这个对象里,最终会将router
返回。在router
中有个重要的install
方法,关于install
的过程可以看之前的文章,这里就不再次介绍了。
对于router
中的各个函数,会在后续文章中继续介绍。