前言
【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 || originalParseQuery
const stringifyQuery = options.stringifyQuery || originalStringifyQuery
const routerHistory = options.history
if (__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/scrollRestoration
if (isBrowser && options.scrollBehavior && 'scrollRestoration' in history) {history.scrollRestoration = 'manual'}
// 标准化 params,转字符串
const normalizeParams = applyToParams.bind(
null,
paramValue => '' + paramValue
)
// 编码 param
const encodeParams = applyToParams.bind(null, encodeParam)
// 解码 params
const 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
中的各个函数,会在后续文章中持续介绍。