3. createRouter()
该方法接收一个 RouterOptions
参数,返回一个 Router
实例。
简化后的代码如下:
// 文件 router.ts
export function createRouter(options: RouterOptions): Router {
...
const router: Router = {
currentRoute,
listening: true,
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
}
1. 输入 RouterOptions
1.1 接口定义
RouterOptions
的接口定义如下:
// 文件 router.ts
export interface RouterOptions extends PathParserOptions {
history: RouterHistory // 路由的 History 实现
routes: Readonly<RouteRecordRaw[]> // 路由列表
scrollBehavior?: RouterScrollBehavior // 路由切换时控制页面的滚动
parseQuery?: typeof originalParseQuery // 自定义 query 解析
stringifyQuery?: typeof originalStringifyQuery // 自定义 stringify query 对象
linkActiveClass?: string // 默认是 router-link-active
linkExactActiveClass?: string // 默认是 router-link-exact-active
}
1.2 history 选项
RouterHistory
接口的定义如下:
// 文件 history/common.ts
export interface RouterHistory {
readonly base: string // 附加到每个 url 前的 base path
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 // 路由事件监听器,当导航从 outside 触发时(比如浏览器的前进和后退按钮)
destroy(): void // 清除路由上所有事件的 listeners
createHref(location: HistoryLocation): string // 生成在 anchor tag 里使用的 href
}
创建 RouterHistory 对象有三种方法:
createWebHashHistory()
:创建一个 hash history,适用于不用 host 或没 server 配合的,但对 SEO 不友好createWebHistory()
:创建一个 HTML5 history,需要后端配置下 servercreateMemoryHistory()
:创建一个基于内存的 history,主要用来处理 SSR,不运行在浏览器端
server {
...
location / {
try_files $uri $uri/ /index.html; # 前端路由的统一入口
}
}
除了对 base
处理的逻辑不同之外,hash 模式和 HTML5 模式的其它逻辑是一样的,因为 Vue Router 4(即 Vue3)对应的浏览器都支持 HTML5 Web History API。
// 文件 src/history/hash.ts
export function createWebHashHistory(base?: string): RouterHistory {
base = location.host ? base || location.pathname + location.search : ''
if (!base.includes('#')) base += '#'
if (__DEV__ && !base.endsWith('#/') && !base.endsWith('#')) {
warn(
`A hash base must end with a "#":\n"${base}" should be "${base.replace(
/#.*$/,
'#'
)}".`
)
}
return createWebHistory(base)
}
对于 createWebHistory()
的介绍,详见 createWebHistory()
。
1.3 routes 选项
在 createRouter()
方法中,只有一个地方用到了 options.routes
。
// 文件 router.ts
export function createRouter(options: RouterOptions): Router {
// matcher 模块,创建一个 Router Matcher
const matcher = createRouterMatcher(options.routes, options)
}
对于 createRouterMatcher()
方法的介绍,详见 createRouterMatcher()
。
2. 输出 Router 实例
2.1 接口定义
Router
实例的接口定义如下:
// Router instance
export interface Router {
// 只读属性
readonly currentRoute: Ref<RouteLocationNormalizedLoaded>
readonly options: RouterOptions // 传给 createRouter() 的原始 options 对象
// 是否关闭对 history 事件的监听,是个底层 API
listening: boolean
// 对 route records 的增删查
addRoute(parentName: RouteRecordName, route: RouteRecordRaw): () => void // 给已经存在的 route 添加一个新的子 route record
addRoute(route: RouteRecordRaw): () => void // 给 router 添加一个新的 route record
removeRoute(name: RouteRecordName): void // 移除
hasRoute(name: RouteRecordName): boolean // 检查是否存在
getRoutes(): RouteRecord[] // 获取所有 route records 的完整列表
// 返回 route location 的 RouteLocation(标准化版本)
resolve(
to: RouteLocationRaw,
currentLocation?: RouteLocationNormalizedLoaded
): RouteLocation & { href: string }
// 通过操作 history 栈,用编程的方式导航到新 URL
push(to: RouteLocationRaw): Promise<NavigationFailure | void | undefined>
replace(to: RouteLocationRaw): Promise<NavigationFailure | void | undefined>
// 在 history 中前进或后退
go(delta: number): void
back(): ReturnType<Router['go']> // 等价于 `router.go(-1)`
forward(): ReturnType<Router['go']> // 等价于 `router.go(1)`
// 导航钩子
beforeEach(guard: NavigationGuardWithThis<undefined>): () => void // 在每次导航之前执行
beforeResolve(guard: NavigationGuardWithThis<undefined>): () => void // 在导航即将被 resolved 之前执行,此时所有组件已成功拉取,导航守卫也已成功
afterEach(guard: NavigationHookAfter): () => void // 在每次导航之后执行
// 错误处理程序,会在导航期间发生未捕获 error 的时候触发
// 包括同步错误和异步错误、在导航守卫中传给 `next`的错误、渲染路由所需异步组件时的错误
onError(handler: _ErrorHandler): () => void
// 当 router 完成了初始导航就会 resolve 该 Promise,包括和初始路由相关的所有异步钩子和异步组件
// 在服务器端渲染时会很有用,因为此时需要是开发人员手动 push 初始 location(客户端时 router 会自动从 URL 中获取到)
isReady(): Promise<void>
// 当执行 `app.use(router)` 时会被自动调用
// 会在客户端触发初始导航
install(app: App): void
}
2.2 install()
install()
3. 内部方法
这部分介绍几个重要的内部方法。
监听 popstate 事件进行前端视图的切换
pushWithRedirect()
重定向导航navigate()
普通导航
// 文件 router.ts
export function createRouter(options: RouterOptions): Router {
function pushWithRedirect(
to: RouteLocationRaw | RouteLocation,
redirectedFrom?: RouteLocation
): Promise<NavigationFailure | void | undefined> { }
function navigate(
to: RouteLocationNormalized,
from: RouteLocationNormalizedLoaded
): Promise<any> { }
return router
}
3.1 pushWithRedirect()
pushWithRedirect()
简化后的代码,如下:
function pushWithRedirect(
to: RouteLocationRaw | RouteLocation,
redirectedFrom?: RouteLocation
): Promise<NavigationFailure | void | undefined> {
const targetLocation: RouteLocation = (pendingLocation = resolve(to))
const from = currentRoute.value
const shouldRedirect = handleRedirectRecord(targetLocation)
// 如果需要重定向,则递归调用
if (shouldRedirect)
return pushWithRedirect(...)
// 否则调用 navigate()
const toLocation = targetLocation as RouteLocationNormalized
toLocation.redirectedFrom = redirectedFrom
return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
.catch(...)
.then(...)
}
3.2 navigate()
navigate()
源码如下:(没有看到明显的组件切换 ???)
function navigate(
to: RouteLocationNormalized,
from: RouteLocationNormalizedLoaded
): Promise<any> {
// 1. 提取路由记录 RouteRecord
const [leavingRecords, updatingRecords, enteringRecords] = extractChangingRecords(to, from)
// 2. guards.push()
let guards: Lazy<any>[]
// 这里所有的组件都已经被 resolved 了一次,因为我们 are leaving
guards = extractComponentsGuards(
leavingRecords.reverse(), // 原地操作,反转
'beforeRouteLeave',
to,
from
)
for (const record of leavingRecords) {
record.leaveGuards.forEach(guard => {
guards.push(guardToPromiseFn(guard, to, from))
})
}
const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(
null,
to,
from
)
guards.push(canceledNavigationCheck)
// 3. 运行每个 route 的 beforeRouteLeave 守卫队列
return (
runGuardQueue(guards)
.then(() => {
// 检查全局守卫, beforeEach
guards = []
for (const guard of beforeGuards.list()) {
guards.push(guardToPromiseFn(guard, to, from))
}
guards.push(canceledNavigationCheck)
return runGuardQueue(guards)
})
.then(() => {
// check in components, beforeRouteUpdate
guards = extractComponentsGuards(
updatingRecords,
'beforeRouteUpdate',
to,
from
)
for (const record of updatingRecords) {
record.updateGuards.forEach(guard => {
guards.push(guardToPromiseFn(guard, to, from))
})
}
guards.push(canceledNavigationCheck)
// 运行每个 route 的 beforeEnter 守卫队列
return runGuardQueue(guards)
})
.then(() => {
// 检查 route 的 beforeEnter
guards = []
for (const record of to.matched) {
// 对于 reused views,不触发 beforeEnter
if (record.beforeEnter && !from.matched.includes(record)) {
if (isArray(record.beforeEnter)) {
for (const beforeEnter of record.beforeEnter)
guards.push(guardToPromiseFn(beforeEnter, to, from))
} else {
guards.push(guardToPromiseFn(record.beforeEnter, to, from))
}
}
}
guards.push(canceledNavigationCheck)
// 运行每个 route 的 beforeEnter 守卫队列
return runGuardQueue(guards)
})
.then(() => {
// 注意:此时 to.matched 已被规范化,且不包含任何 () => Promise<Component>
// 清除现有的 enterCallbacks, 它们是由 extractComponentsGuards 添加的
to.matched.forEach(record => (record.enterCallbacks = {}))
// check in-component beforeRouteEnter
guards = extractComponentsGuards(
enteringRecords,
'beforeRouteEnter',
to,
from
)
guards.push(canceledNavigationCheck)
// 运行每个 route 的 beforeEnter 守卫队列
return runGuardQueue(guards)
})
.then(() => {
// 检查全局守卫, beforeResolve
guards = []
for (const guard of beforeResolveGuards.list()) {
guards.push(guardToPromiseFn(guard, to, from))
}
guards.push(canceledNavigationCheck)
return runGuardQueue(guards)
})
// catch 所有取消的any navigation
.catch(err =>
isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED)
? err
: Promise.reject(err)
)
)
}
function runGuardQueue(guards: Lazy<any>[]): Promise<void> {
return guards.reduce(
(promise, guard) => promise.then(() => guard()),
Promise.resolve()
)
}
相关 types 定义如下:
// 文件 matcher/types.ts
export interface RouteRecordNormalized {
path: _RouteRecordBase['path']
redirect: _RouteRecordBase['redirect'] | undefined
name: _RouteRecordBase['name']
components: RouteRecordMultipleViews['components'] | null | undefined
children: RouteRecordRaw[] // 嵌套的 route records
meta: Exclude<_RouteRecordBase['meta'], void>
props: Record<string, _RouteRecordProps>
beforeEnter: _RouteRecordBase['beforeEnter'] // 注册 beforeEnter guards
leaveGuards: Set<NavigationGuard> // 注册 leave guards
updateGuards: Set<NavigationGuard> // 注册 update guards
enterCallbacks: Record<string, NavigationGuardNextCallback[]> // 注册 beforeRouteEnter 回调
instances: Record<string, ComponentPublicInstance | undefined | null> // 挂载的 route component 实例
aliasOf: RouteRecordNormalized | undefined
}
export type RouteRecord = RouteRecordNormalized
Last updated