前言

【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中提取parseQuerystringifyQueryhistory属性,如果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}

而后申明了大量的函数,包含addRouteremoveRoutegetRoutes等,这些函数也就是咱们日常应用的addRouteremoveRoute等。

createRouter的最初创立了一个router对象,并将其返回,该对象简直蕴含了申明的所有函数。

总结

createRouter函数中申明了一些全局钩子所需的变量和很多函数,这些函数就是咱们日常应用的一些办法,如addRouteremoveRoute等,在函数的最初,申明了一个router对象,后面所申明的函数少数都会被蕴含在这个对象里,最终会将router返回。在router中有个重要的install办法,对于install的过程能够看之前的文章,这里就不再次介绍了。
对于router中的各个函数,会在后续文章中持续介绍。