vue-router4源码解析(1)

2,122 阅读6分钟

vue-router4 github源码地址

一、入口文件分析

源码的入口文件,通常可以通过package.json文件的scripts中得知:

{
    "scripts": {
        "build": "rollup -c rollup.config.js",
        ...
        "dev": "webpack serve --mode=development",
        ...
    }
}

在源码的scripts配置中,通过build命令可以得知以下信息

  • 生产版本通过rollup构建工具进行打包
  • 可以通过rollup.config.js文件获取入口文件信息 在rollup.config.js文件中查找input配置,得到入口文件地址为src/index.ts
{
    input: `src/index.ts`,
}

二、入口文件导出的模块

打开入口文件src/index.ts,源代码如下:

export { createWebHistory } from './history/html5'
export { createMemoryHistory } from './history/memory'
export { createWebHashHistory } from './history/hash'
export { createRouterMatcher, RouterMatcher } from './matcher'

export {
  LocationQuery,
  parseQuery,
  stringifyQuery,
  LocationQueryRaw,
  LocationQueryValue,
  LocationQueryValueRaw,
} from './query'

export { RouterHistory, HistoryState } from './history/common'

export { RouteRecord, RouteRecordNormalized } from './matcher/types'

export {
  PathParserOptions,
  _PathParserOptions,
} from './matcher/pathParserRanker'

export {
  routeLocationKey,
  routerViewLocationKey,
  routerKey,
  matchedRouteKey,
  viewDepthKey,
} from './injectionSymbols'

export {
  // route location
  _RouteLocationBase,
  LocationAsPath,
  LocationAsRelativeRaw,
  RouteQueryAndHash,
  RouteLocationRaw,
  RouteLocation,
  RouteLocationNormalized,
  RouteLocationNormalizedLoaded,
  RouteParams,
  RouteParamsRaw,
  RouteParamValue,
  RouteParamValueRaw,
  RouteLocationMatched,
  RouteLocationOptions,
  RouteRecordRedirectOption,
  // route records
  _RouteRecordBase,
  RouteMeta,
  START_LOCATION_NORMALIZED as START_LOCATION,
  RouteComponent,
  // RawRouteComponent,
  RouteRecordName,
  RouteRecordRaw,
  NavigationGuard,
  NavigationGuardNext,
  NavigationGuardWithThis,
  NavigationHookAfter,
} from './types'

export {
  createRouter,
  Router,
  RouterOptions,
  RouterScrollBehavior,
} from './router'

export {
  NavigationFailureType,
  NavigationFailure,
  isNavigationFailure,
} from './errors'

export { onBeforeRouteLeave, onBeforeRouteUpdate } from './navigationGuards'
export {
  RouterLink,
  useLink,
  RouterLinkProps,
  UseLinkOptions,
} from './RouterLink'
export { RouterView, RouterViewProps } from './RouterView'

export * from './useApi'
export * from './globalExtensions'

我们可以从export出的内容看出可以分为以下几类API

  • history 模块
  • matcher 模块
  • router 模块
  • RouterLink 模块
  • RouterView 模块
  • errors 模块
  • navigationGuards 模块
  • 其他
    • injectionSymbols
    • types
    • useApi
    • globalExtensions

    三、基础

    对应使用文档地址:next.router.vuejs.org/zh/guide/

    1、createRouter创建路由实例

    在文档提供的示例代码中,路由配置的定义与VueRouter3基本没有变化,但是路由实例,是通过执行createRouter(options)方法创建的
const Home = { template: '<div>Home</div>' }
const About = { template: '<div>About</div>' }
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
]
const router = VueRouter.createRouter({
  history: VueRouter.createWebHashHistory(),
  routes,
})

在入口文件中找到createRouter方法,它是通过router模块导出的,router模块源码路径为src/router.ts,在该文件中找到createRouter方法源码,简化后代码如下。 简而言之,该方法就是传入RouterOptions类型的对象,然后返回一个Router实例。

 export function createRouter(options: RouterOptions): Router {
  const router: Router = {
    currentRoute,

    addRoute,
    removeRoute,
    hasRoute,
    getRoutes,
    resolve,
    options,

    push,
    replace,
    go,
    back: () => go(-1),
    forward: () => go(1),

    beforeEach: beforeGuards.add,
    beforeResolve: beforeResolveGuards.add,
    afterEach: afterGuards.add,

    onError: errorHandlers.add,
    isReady,

    install(app: App) {
      // ...
    },
  }

  return router
}

2、参数:RouterOptions

createRouter()方法只有一个options对象参数,类型是RouterOptions

// src/router.ts
export interface RouterOptions extends PathParserOptions {
  history: RouterHistory
  routes: RouteRecordRaw[]
  scrollBehavior?: RouterScrollBehavior
  parseQuery?: typeof originalParseQuery
  stringifyQuery?: typeof originalStringifyQuery
  linkActiveClass?: string
  linkExactActiveClass?: string
}

从接口定义可以看出,必须包含的对象属性为: - history:用于路由实现历史记录,类型为RouterHistory

  • routes:应该添加到路由的初始路由列表,类型为 RouteRecordRaw 以下属性为非必需属性:
  • scrollBehavior:在页面之间导航时控制滚动的函数。可以返回一个 Promise 来延迟滚动
  • parseQuery:用于解析查询的自定义实现。必须解码查询键和值。参见对应的 stringifyQuery
  • stringifyQuery:对查询对象进行字符串化的自定义实现。不应该在前面加上 ?。应该正确编码查询键和- 值。 parseQuery 对应于处理查询解析。
  • linkActiveClass:用于激活的 RouterLink 的默认类。如果什么都没提供,则会使用 router-link-active
  • linkExactActiveClass:用于精准激活的 RouterLink 的默认类。如果什么都没提供,则会使用 router-link-exact-active 下面分析下historyroutes这两个属性

2-1、history

RouterHistory的接口定义如下:

interface RouterHistory {
  // 只读属性,基本路径,会添加到每个url的前面
  readonly base: string
  // 只读属性,当前路由
  readonly location: HistoryLocation
  // 只读属性,当前状态
  readonly state: HistoryState
  // 路由跳转方法
  push(to: HistoryLocation, data?: HistoryState): void
  // 路由跳转方法
  replace(to: HistoryLocation, data?: HistoryState): void
  // 路由跳转方法
  go(delta: number, triggerListeners?: boolean): void
  // 添加一个路由事件监听器
  listen(callback: NavigationCallback): () => void
  // 生成在锚点标签中使用的href的方法
  createHref(location: HistoryLocation): string
  // 清除listeners
  destroy(): void
}

VueRouter提供三种方法创建RouterHistory对象: -createWebHashHistory(): 创建一个 hash 历史记录。对于没有主机的 web 应用程序 (例如 file://),或当配置服务器不能处理任意 URL 时这非常有用。注意:如果 SEO 对你很重要,你应该使用 createWebHistory

-createWebHistory(): 创建一个 HTML5 历史,即单页面应用程序中最常见的历史记录。应用程序必须通过 http 协议被提供服务

-createMemoryHistory():创建一个基于内存的历史记录。这个历史记录的主要目的是处理 SSR。它在一个特殊的位置开始,这个位置无处不在。如果用户不在浏览器上下文中,它们可以通过调用 router.push()router.replace() 将该位置替换为启动位置

换一句话说,在创建VueRouter实例时,options.history参数为以上三种的其中一种或者自定义方法(需要返回RouterHistory类型对象)

1、createWebHashHistory

(1)base

在上面提供的例子中,调用createWebHashHistory方法时没有传入任何参数,访问地址是 http://localhost:8080 ,则此时base为'/',此时没有#,所以base会再追加一个#符号。接着调用createWebHistory函数继续创建其他属性或者方法

base = location.host ? base || location.pathname + location.search : ''
if (base.indexOf('#') < 0) 
    base += '#'
return createWebHistory(base)

(2)其他属性和方法

除了base属性,其他属性和方法都是通过createWebHistory(base)方法进行创建的,所以其他属性和方法,在createWebHistory(base)中进行分析

2、createWebHistory

(1)base

首先对base进行格式化,上面在调用createWebHashHistory创建hash模式history对象时,传进来的base的值为'/#',调用normalizeBase函数后,得到的base依然是'/#'

如果是在创建VueRouter实例时,调用createWebHistory()创建history对象,则此时baseundefined,调用normalizeBase格式化后的base为空字符串''

base = normalizeBase(base)

normalizeBase方法代码如下:

// src/utils/env.ts
export const isBrowser = typeof window !== 'undefined'

// src/history/common.ts
function normalizeBase(base?: string): string {
  if (!base) {
    if (isBrowser) {
      const baseEl = document.querySelector('base')
      base = (baseEl && baseEl.getAttribute('href')) || '/'
      base = base.replace(/^\w+:\/\/[^\/]+/, '')
    } else {
      base = '/'
    }
  }
  if (base[0] !== '/' && base[0] !== '#') base = '/' + base
  return removeTrailingSlash(base)
}

// src/location.ts
const TRAILING_SLASH_RE = /\/$/
export const removeTrailingSlash = (path: string) => path.replace(TRAILING_SLASH_RE, '')

2)其他属性和方法的创建

createWebHistory方法中,通过调用useHistoryStateNavigation(base)方法,返回一个包含location,state,push,replace属性和方法的对象

// src/history/html5.ts
const historyNavigation = useHistoryStateNavigation(base)

然后通过调用useHistoryListeners(...)函数,返回pauseListeners,listen,destroy方法的对象。

const historyListeners = useHistoryListeners(
    base,
    historyNavigation.state,
    historyNavigation.location,
    historyNavigation.replace
  )

接着声明go()方法:

function go(delta: number, triggerListeners = true) {
    // ...
}

然后将以上两个方法的到的对象以及默认对象组合成routerHistory对象,此时routerHistory对象创建完成

// src/history/html5.ts
const routerHistory: RouterHistory = assign(
  {
    // it's overridden right after
    location: '',
    base,
    go,
    createHref: createHref.bind(null, base),
  },

  historyNavigation,
  historyListeners
)

最后为location,state这两个属性添加getter,读取这两个属性值时,返回该对象的value属性值

Object.defineProperty(routerHistory, 'location', {
  enumerable: true,
  get: () => historyNavigation.location.value,
})

Object.defineProperty(routerHistory, 'state', {
  enumerable: true,
  get: () => historyNavigation.state.value,
})

hashhistory路由模式,除了base的处理逻辑不同,其他属性或者方法使用的是共同的逻辑。现在了解了创建RouterHistory对象的整体流程,然后再具体分析除了base之外的属性或者方法的实现逻辑。

今天先就这么多,溜了