美文网首页【vue-rouer源码】
【vue-router源码】四、createRouter源码解析

【vue-router源码】四、createRouter源码解析

作者: MAXLZ | 来源:发表于2022-06-07 09:03 被阅读0次

    前言

    【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 || 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
    }
    

    然后声明了大量的函数,包括addRouteremoveRoutegetRoutes等,这些函数也就是我们日常使用的addRouteremoveRoute等。

    createRouter的最后创建了一个router对象,并将其返回,该对象几乎包含了声明的所有函数。

    总结

    createRouter函数中声明了一些全局钩子所需的变量和很多函数,这些函数就是我们日常使用的一些方法,如addRouteremoveRoute等,在函数的最后,声明了一个router对象,前面所声明的函数多数都会被包含在这个对象里,最终会将router返回。在router中有个重要的install方法,关于install的过程可以看之前的文章,这里就不再次介绍了。
    对于router中的各个函数,会在后续文章中继续介绍。

    相关文章

      网友评论

        本文标题:【vue-router源码】四、createRouter源码解析

        本文链接:https://www.haomeiwen.com/subject/teosmrtx.html