前言
【vue-router源码】系列文章将带你从0开始理解vue-router
的具体实现。该系列文章源码参考vue-router v4.0.15
。
源码地址:https://github.com/vuejs/router
浏览该文章的前提是你最好理解vue-router
的根本应用,如果你没有应用过的话,可通过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 },})
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}
咱们来看下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.' )
紧接着申明了一些全局守卫相干的变量,和一些对于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)
对于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, }}
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}
而后申明了大量的函数,包含addRoute
、removeRoute
、getRoutes
等,这些函数也就是咱们日常应用的addRoute
、removeRoute
等。
在createRouter
的最初创立了一个router
对象,并将其返回,该对象简直蕴含了申明的所有函数。
总结
createRouter
函数中申明了一些全局钩子所需的变量和很多函数,这些函数就是咱们日常应用的一些办法,如addRoute
、removeRoute
等,在函数的最初,申明了一个router
对象,后面所申明的函数少数都会被蕴含在这个对象里,最终会将router
返回。在router
中有个重要的install
办法,对于install
的过程能够看之前的文章,这里就不再次介绍了。
对于router
中的各个函数,会在后续文章中持续介绍。