前言
本文是vue-router源码阅读系列的第一章, 主要介绍了vue-router的源码入口概览. 系列其他文章如下:
vue-router的使用
// ts文件中
import { createRouter } from "vue-router";
const router = createRouter({});
createApp(App).use(router);
// vue文件中
import { useRouter } from "vue-router";
const router = useRouter();
从上面代码中可以看到, 使用vue-router需要先用#createRouter创建一个router实例, 然后将其作为插件使用. 而在vue文件中使用router需要调用#useRouter来获取一个router实例.
从 createRouter 开始
先从createRouter()开始来学习vue-router的具体实现.
export function createRouter(options: RouterOptions): Router
上面是#createRouter的函数签名, 该函数接收一个RouterOptions配置参数对象, 返回一个Router对象, 也就是供vue使用的router插件.
Router的接口如下:
export interface Router {
readonly currentRoute: Ref<RouteLocationNormalizedLoaded>;
readonly options: RouterOptions;
listening: boolean;
addRoute(parentName: RouteRecordName, route: RouteRecordRaw): () => void;
addRoute(route: RouteRecordRaw): () => void;
removeRoute(name: RouteRecordName): void;
hasRoute(name: RouteRecordName): boolean;
getRoutes(): RouteRecord[];
resolve(
to: RouteLocationRaw,
currentLocation?: RouteLocationNormalizedLoaded
): RouteLocation & { href: string };
push(to: RouteLocationRaw): Promise<NavigationFailure | void | undefined>;
replace(to: RouteLocationRaw): Promise<NavigationFailure | void | undefined>;
back(): ReturnType<Router["go"]>;
forward(): ReturnType<Router["go"]>;
go(delta: number): void;
beforeEach(guard: NavigationGuardWithThis<undefined>): () => void;
beforeResolve(guard: NavigationGuardWithThis<undefined>): () => void;
afterEach(guard: NavigationHookAfter): () => void;
onError(handler: _ErrorHandler): () => void;
isReady(): Promise<void>;
install(app: App): void;
}
可以看到, router是一个拥有#install的对象, 可作为插件使用. 接下来就从#createRouter开始, 查看其是如何返回一个router对象的.
下面是#createRouter的返回部分代码, 其创建了一个Router, 将在函数体内部实现的方法赋值到了Router的同名属性中, 可以看到整个router的内容可分为六大类:
- 属性
- route相关方法, 该部分方法主要用于操作router中的route
- history相关方法, 该部分方法用于页面的跳转
- 路由守卫
- 事件
- install方法
const router: Router = {
currentRoute, // 当前路由位置, 默认值为 START_LOCATION_NORMALIZED, 定义在 '@/types/index.ts'
listening: true, // 监听历史事件
// route相关方法
addRoute,
removeRoute,
hasRoute,
getRoutes,
resolve,
options,
// history相关方法
push,
replace,
go,
back: () => go(-1),
forward: () => go(1),
// 路由守卫
beforeEach: beforeGuards.add,
beforeResolve: beforeResolveGuards.add,
afterEach: afterGuards.add,
// 事件监听器
onError: errorHandlers.add,
isReady,
// 在 app.use(router) 执行时被自动调用,
// 在客户端调用时触发初始化导航
install(app: App) {
const router = this
// 添加路由组件 RouterLink 和 RouterView
app.component('RouterLink', RouterLink)
app.component('RouterView', RouterView)
// 添加全局属性 $router
app.config.globalProperties.$router = router
Object.defineProperty(app.config.globalProperties, '$route', {
enumerable: true,
get: () => unref(currentRoute),
})
// 初始化导航, 仅在环境是客户端浏览器时执行, 在服务器端执行可能会导致错误
if (
isBrowser &&
// 避免在拥有多个 app 实例时多次进行初始化导航
!started &&
currentRoute.value === START_LOCATION_NORMALIZED
) {
started = true
push(routerHistory.location).catch(err => {
if (__DEV__) warn('Unexpected error when starting the router:', err)
})
}
// 遍历 START_LOCATION_NORMALIZED 对象, 将其键复制到 reactiveRouter 中
// 并将其变为计算属性便于追踪
const reactiveRoute = {} as {
[k in keyof RouteLocationNormalizedLoaded]: ComputedRef<
RouteLocationNormalizedLoaded[k]
>
}
for (const key in START_LOCATION_NORMALIZED) {
// @ts-expect-error: the key matches
reactiveRoute[key] = computed(() => currentRoute.value[key])
}
// 将 router, reactiveRoute, currentRoute 暴露给所有组件, 键为 symbol
// 因为 <setup> 中无法访问 this, 因此再次使用 provide 暴露
app.provide(routerKey, router)
app.provide(routeLocationKey, reactive(reactiveRoute))
app.provide(routerViewLocationKey, currentRoute)
const unmountApp = app.unmount
// 将 app 加入到已加载的 Apps 集合中保存
installedApps.add(app)
// 设定 app 注销时的行为
app.unmount = function () {
// 将 app 从集合中移除
installedApps.delete(app)
// 不存在任何 app 使用 router 时将 router 重置
if (installedApps.size < 1) {
pendingLocation = START_LOCATION_NORMALIZED
removeHistoryListener && removeHistoryListener()
removeHistoryListener = null
currentRoute.value = START_LOCATION_NORMALIZED
started = false
ready = false
}
// 注销 app
unmountApp()
}
if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && isBrowser) {
addDevtools(app, router, matcher)
}
},
}
return router
#install中主要实现了以下功能
- 添加两个路由组件router-link和router-view
- 添加vue全局属性$route, 值为当前页面路由
- 使用provide暴露router, currentRoute和reactiveRoute
- 设定app注销时行为