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
下面分析下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
之外的属性或者方法的实现逻辑。
今天先就这么多,溜了