注册
Vue 从它的设计上就是一个渐进式 JavaScript 框架,它本身的核心是解决视图渲染的问题,其它的能力就通过插件的方式来解决。Vue-Router 就是官方维护的路由插件,在介绍它的注册实现之前,我们先来分析一下 Vue3 通用的插件注册原理。
插件
插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。下面是如何安装 vue-router 的示例:
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
routes: [],
history: createWebHistory('/'),
scrollBehavior: () => ({ top: 0 })
})
const app = createApp(App)
app.use(router)
一个插件可以是一个拥有 install() 方法的对象,也可以直接是一个安装函数本身。安装函数会接收到安装它的应用实例和传递给 app.use() 的额外选项作为参数:
const customPlugin = {
install(app, options) {
// 配置此应用
}
}
插件没有严格定义的使用范围,但是插件发挥作用的常见场景主要包括以下几种:
- 通过
app.component()和app.directive()注册一到多个全局组件或自定义指令。 - 通过
app.provide()使一个资源可被注入进整个应用。 - 向
app.config.globalProperties中添加一些全局实例属性或方法 - 一个可能上述三种都包含了的功能库 (例如
vue-router)。
app.use()
Vue3 提供了实例方法 use()
来安装一个插件,定义在 apiCreateApp.ts
中:
const app: App = (context.app = {
use(plugin: Plugin, ...options: any[]) {
// installedPlugins Set 类型存储已经安装的插件
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied to target app.`)
} else if (plugin && isFunction(plugin.install)) {
// plugin && isFunction(plugin.install) 拥有 install() 方法的对象
installedPlugins.add(plugin)
// 执行install方法,将app实例作为第一个参数
plugin.install(app, ...options)
} else if (isFunction(plugin)) {
installedPlugins.add(plugin)
// 插件本身就是一个函数,将app实例作为第一个参数
plugin(app, ...options)
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`
)
}
return app
}
})
app.use() 接受一个 plugin 参数和一个 options 参数,并且维护一个 installedPlugins Set,用来存储已经注册的plugin;注册流程如下:
- 判断是否 plugin
是否已经注册,开发环境下对已经注册的插件再次注册,会打印警告
Plugin has already been applied to target app. - 判断 plugin 是否定义 install 函数,有则将插件添加到 installedPlugins Set集合中,调用 install 方法,并将 app 实例作为第一个参数
- 不存在 install 方法,判断 plugin 自身是否是一个函数,是则将插件添加到 installedPlugins Set集合中,将 app 实例作为第一个参数调用该函数
- 以上条件都不是,在开发环境下,打印警告
A plugin must either be a function or an object with an "install" function.
路由注册
在 Vue Router 4 中,router 对象提供了 install 方法,这是一个插件安装方法,用于将 vue-router 安装到 Vue
应用程序中。定义在 router.ts 中:
const router: Router = {
install(app: App) {
const router = this
// 注册两个全局组件:RouterView和RouterLink。
app.component('RouterLink', RouterLink)
app.component('RouterView', RouterView)
// 在app实例上注入了$router和$route
app.config.globalProperties.$router = router
Object.defineProperty(app.config.globalProperties, '$route', {
enumerable: true,
get: () => unref(currentRoute),
})
// 检查客户端环境下的初始导航逻辑
if (
isBrowser && !started && currentRoute.value === START_LOCATION_NORMALIZED) {
started = true
push(routerHistory.location).catch(err => {
if (__DEV__) warn('Unexpected error when starting the router:', err)
})
}
// 提供响应式的路由location对象
const reactiveRoute = {} as RouteLocationNormalizedLoaded
for (const key in START_LOCATION_NORMALIZED) {
Object.defineProperty(reactiveRoute, key, {
get: () => currentRoute.value[key as keyof RouteLocationNormalized],
enumerable: true,
})
}
// 使用app.provide注入router实例
app.provide(routerKey, router)
app.provide(routeLocationKey, shallowReactive(reactiveRoute))
app.provide(routerViewLocationKey, currentRoute)
const unmountApp = app.unmount
installedApps.add(app)
// 注册unmount钩子函数
app.unmount = function () {
installedApps.delete(app)
if (installedApps.size < 1) {
pendingLocation = START_LOCATION_NORMALIZED
removeHistoryListener && removeHistoryListener()
removeHistoryListener = null
currentRoute.value = START_LOCATION_NORMALIZED
started = false
ready = false
}
unmountApp()
}
// 在开发环境下添加devtools支持
if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && isBrowser) {
addDevtools(app, router, matcher)
}
}
}
总结
上面我们分析了,Vue 插件的注册机制,Vue 插件编写时通常提供 install 方法,通过 app.use(plugin) 的时候,就会执行 install 方法。Vue-Router,在通过 createRouter() 方法,创建 router 实例时,会返回一个包含 install 方法的对象。 router 中 install 方法中主要做了以下工作:
- 注册了RouterLink和RouterView组件
- 在app实例上注入了$router和$route
- 检查客户端环境下的初始导航逻辑
- 提供响应式的路由location对象
- 使用app.provide注入router实例
- 注册unmount钩子函数
- 在开发环境下添加devtools支持
