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模块- 其他
injectionSymbolstypesuseApiglobalExtensions
三、基础
对应使用文档地址: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:用于解析查询的自定义实现。必须解码查询键和值。参见对应的stringifyQuerystringifyQuery:对查询对象进行字符串化的自定义实现。不应该在前面加上 ?。应该正确编码查询键和- 值。parseQuery对应于处理查询解析。linkActiveClass:用于激活的RouterLink的默认类。如果什么都没提供,则会使用router-link-activelinkExactActiveClass:用于精准激活的RouterLink的默认类。如果什么都没提供,则会使用router-link-exact-active下面分析下history和routes这两个属性
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对象,则此时base时undefined,调用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,
})
hash和history路由模式,除了base的处理逻辑不同,其他属性或者方法使用的是共同的逻辑。现在了解了创建RouterHistory对象的整体流程,然后再具体分析除了base之外的属性或者方法的实现逻辑。
今天先就这么多,溜了