各位靓仔靓女们,早上好/下午好/晚上好! 今天咱们来聊聊Vue 3里那个神奇的app
实例,以及围绕它展开的Global API。 保证听完之后,你对app.use
、app.component
等等这些API的内部运作,以及app
实例的上下文,有个更清晰的认识,以后写代码也能更加自信。
咱们先来热个身,回顾一下app
实例是个什么玩意儿。
app
实例:Vue 3世界的入口
在Vue 2里,我们用new Vue()
来创建一个根实例。但在Vue 3,我们有了更优雅的方式:createApp()
。
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App) // 创建一个App实例
app.mount('#app') // 挂载到id为app的元素上
这个app
实例,就像是你家的大门,通过它,你可以注册全局组件、指令、插件,配置各种选项,最终控制整个Vue应用。 它的作用域是整个应用,而不是某个组件。 这跟Vue 2里直接在Vue构造函数上挂载静态方法不一样,Vue 3更强调实例的独立性。
app.use
:插件的秘密通道
app.use
是一个非常常用的API,用来注册Vue插件。 插件通常用来扩展Vue的功能,比如注册全局组件、添加全局指令、提供全局方法等等。
import { createApp } from 'vue'
import App from './App.vue'
// 假设我们有一个插件 myPlugin
const myPlugin = {
install: (app, options) => {
// 添加全局组件
app.component('MyComponent', {
template: '<div>This is a global component</div>'
})
// 添加全局指令
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 添加全局方法 (挂载到 app.config.globalProperties)
app.config.globalProperties.$myMethod = (msg) => {
alert('Message from plugin: ' + msg)
}
// 打印插件的配置项
console.log('Plugin options:', options)
}
}
const app = createApp(App)
app.use(myPlugin, { message: 'Hello from main.js!' }) // 注册插件,并传递配置项
app.mount('#app')
那么,app.use
内部到底做了些什么呢? 让我们扒开它的源码看看:
// packages/runtime-core/src/apiCreateApp.ts
function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
const app: App<HostElement> = {
// ... 其他属性和方法
use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
warn(`Plugin has already been applied to target app.`)
return app
}
if (isFunction(plugin)) {
plugin(app, ...options)
} else if (isObject(plugin) && isFunction(plugin.install)) {
plugin.install(app, ...options)
} else {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`
)
}
installedPlugins.add(plugin)
return app
},
// ... 其他属性和方法
}
return app
}
}
简化一下,核心逻辑是这样的:
- 检查插件是否已经安装:
installedPlugins.has(plugin)
防止重复安装。 - 执行插件的安装逻辑:
- 如果插件是一个函数,直接调用
plugin(app, ...options)
。 - 如果插件是一个对象,并且有
install
方法,调用plugin.install(app, ...options)
。
- 如果插件是一个函数,直接调用
- 记录已安装的插件:
installedPlugins.add(plugin)
,防止重复安装。 - 返回
app
实例: 支持链式调用。
所以,插件的本质就是一个拥有 install
方法的对象,或者直接就是一个函数。 install
方法接收 app
实例作为参数,你可以在这个方法里做任何你想做的事情,比如注册组件、指令、添加全局属性等等。
app.component
:全局组件的舞台
app.component
用于注册全局组件。 注册之后,你可以在任何组件的模板中使用这个组件,无需手动导入。
import { createApp } from 'vue'
import App from './App.vue'
// 注册一个全局组件
app.component('MyButton', {
template: '<button>Click me!</button>'
})
const app = createApp(App)
app.mount('#app')
app.component
的内部实现也比较简单:
// packages/runtime-core/src/apiCreateApp.ts
function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext() // 创建一个应用上下文
const app: App<HostElement> = {
// ... 其他属性和方法
component(name: string, component?: Component): any {
if (!component) {
return context.components[name]
}
if (__DEV__ && context.components[name]) {
warn(`Component "${name}" has already been registered in the app.`)
}
context.components[name] = component
return app
},
// ... 其他属性和方法
}
return app
}
}
// packages/runtime-core/src/createApp.ts
export function createAppContext(): AppContext {
return {
app: null as any,
config: {
isNativeTag: NO,
performance: false,
globalProperties: {},
optionMergeStrategies: {},
errorHandler: undefined,
warnHandler: undefined,
compilerOptions: null,
runtimeCompiler: false,
// 2.7-only options
transitionOptions: undefined,
directives: {},
components: {}, // 用于存储全局组件
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null),
optionsCache: new WeakMap(),
propsCache: new WeakMap(),
emitsCache: new WeakMap(),
renderCache: new WeakMap(),
directivesCache: new WeakMap(),
providesCache: new WeakMap(),
plugin: null,
defaults: {
slots: {},
props: {}
},
effects: [],
reload: NOOP,
isNativeTag: NO
}
}
简化一下,核心逻辑是:
- 创建一个应用上下文
context
: 这个context
包含了应用的所有全局配置和状态,比如全局组件、全局指令、全局属性等等。 - 将组件注册到
context.components
:context.components
是一个对象,用于存储全局组件,键是组件名,值是组件定义。 - 返回
app
实例: 支持链式调用。
当你调用 app.component('MyButton', { ... })
时,实际上是将 MyButton
组件注册到了 context.components
中。 然后在编译模板时,Vue会从 context.components
中查找对应的组件定义。
app.directive
:全局指令的魔法棒
app.directive
用于注册全局指令。 全局指令可以在任何组件的模板中使用,无需手动导入。
import { createApp } from 'vue'
import App from './App.vue'
// 注册一个全局指令
app.directive('highlight', {
mounted(el, binding) {
el.style.backgroundColor = binding.value
}
})
const app = createApp(App)
app.mount('#app')
app.directive
的内部实现和 app.component
非常相似:
// packages/runtime-core/src/apiCreateApp.ts
function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext()
const app: App<HostElement> = {
// ... 其他属性和方法
directive(name: string, directive?: Directive): any {
if (!directive) {
return context.directives[name]
}
if (__DEV__ && context.directives[name]) {
warn(`Directive "${name}" has already been registered in the app.`)
}
context.directives[name] = directive
return app
},
// ... 其他属性和方法
}
return app
}
}
简化一下,核心逻辑是:
- 将指令注册到
context.directives
:context.directives
是一个对象,用于存储全局指令,键是指令名,值是指令定义。 - 返回
app
实例: 支持链式调用。
当你调用 app.directive('highlight', { ... })
时,实际上是将 highlight
指令注册到了 context.directives
中。 然后在编译模板时,Vue会从 context.directives
中查找对应的指令定义。
app.config
:应用的全局配置
app.config
允许你配置应用的全局行为,比如错误处理、警告处理、全局属性等等。
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 配置全局错误处理
app.config.errorHandler = (err, instance, info) => {
console.error('Global error handler:', err, instance, info)
}
// 配置全局属性
app.config.globalProperties.$myGlobal = 'Hello from globalProperties'
app.mount('#app')
app.config
实际上指向的是 AppContext
里的 config
属性。
// packages/runtime-core/src/createApp.ts
export function createAppContext(): AppContext {
return {
app: null as any,
config: {
isNativeTag: NO,
performance: false,
globalProperties: {}, // 用于存储全局属性
optionMergeStrategies: {},
errorHandler: undefined,
warnHandler: undefined,
compilerOptions: null,
runtimeCompiler: false,
// 2.7-only options
transitionOptions: undefined,
directives: {},
components: {},
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null),
optionsCache: new WeakMap(),
propsCache: new WeakMap(),
emitsCache: new WeakMap(),
renderCache: new WeakMap(),
directivesCache: new WeakMap(),
providesCache: new WeakMap(),
plugin: null,
defaults: {
slots: {},
props: {}
},
effects: [],
reload: NOOP,
isNativeTag: NO
}
}
app.config.errorHandler
:用于配置全局错误处理函数。app.config.warnHandler
:用于配置全局警告处理函数。app.config.globalProperties
:用于添加全局属性,可以在任何组件的模板中使用,比如{{ $myGlobal }}
。app.config.compilerOptions
:用于配置编译器选项。
app.mixin
:全局混入
app.mixin
允许你将一些选项混入到每个组件中。 谨慎使用,因为它会影响到所有的组件。
import { createApp } from 'vue'
import App from './App.vue'
app.mixin({
created() {
console.log('Mixin created!')
}
})
const app = createApp(App)
app.mount('#app')
app.mixin
的内部实现也很简单:
// packages/runtime-core/src/apiCreateApp.ts
function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext()
const app: App<HostElement> = {
// ... 其他属性和方法
mixin(mixin: ComponentOptions): App<HostElement> {
if (__DEV__ && !isObject(mixin)) {
warn(`Mixin must be an object.`)
}
context.mixins.push(mixin)
return app
},
// ... 其他属性和方法
}
return app
}
}
app.mixin
只是简单地将混入对象添加到 context.mixins
数组中。 然后在创建组件实例时,Vue会将 context.mixins
中的所有混入对象合并到组件的选项中。
app
上下文:AppContext
我们一直在说app
上下文,也就是 AppContext
,它到底是什么? 让我们再回顾一下 AppContext
的结构:
export interface AppContext {
app: App<any> | null
config: AppConfig
mixins: ComponentOptions[]
components: Record<string, Component>
directives: Record<string, Directive>
provides: Record<string | symbol, any>
optionsCache: WeakMap<ComponentOptions, InternalComponentOptions>
propsCache: WeakMap<Component, PropObject>
emitsCache: WeakMap<Component, ObjectEmitsOptions | null>
renderCache: WeakMap<Component, InternalRenderFunction>
directivesCache: WeakMap<Component, Record<string, Directive>>
providesCache: WeakMap<Component, Record<string | symbol, any>>
plugin: Plugin | null
defaults: VNodeProps
effects: ReactiveEffect[]
reload: () => void
/**
* @internal
*/
isNativeTag: (tag: string) => boolean
}
属性名 | 类型 | 描述 |
---|---|---|
app |
App<any> | null |
指向 app 实例本身。 |
config |
AppConfig |
应用的全局配置,包含 errorHandler 、warnHandler 、globalProperties 等。 |
mixins |
ComponentOptions[] |
全局混入对象数组。 |
components |
Record<string, Component> |
全局组件注册表,键是组件名,值是组件定义。 |
directives |
Record<string, Directive> |
全局指令注册表,键是指令名,值是指令定义。 |
provides |
Record<string | symbol, any> |
全局 provide 对象,用于祖先组件向后代组件提供依赖。 |
optionsCache |
WeakMap<ComponentOptions, ...> |
组件选项缓存。 |
propsCache |
WeakMap<Component, PropObject> |
组件 props 缓存。 |
emitsCache |
WeakMap<Component, ...> |
组件 emits 缓存。 |
renderCache |
WeakMap<Component, ...> |
组件渲染函数缓存。 |
directivesCache |
WeakMap<Component, ...> |
组件指令缓存。 |
providesCache |
WeakMap<Component, ...> |
组件 provide 缓存。 |
plugin |
Plugin | null |
当前已安装的插件。 |
defaults |
VNodeProps |
默认的 VNode 属性。 |
effects |
ReactiveEffect[] |
响应式副作用数组。 |
reload |
() => void |
重新加载应用的函数 (通常用于开发环境)。 |
isNativeTag |
(tag: string) => boolean |
判断是否是原生 HTML 标签的函数。 |
AppContext
是Vue 3应用的核心上下文,它包含了应用的所有全局配置和状态。 通过 app
实例,我们可以访问和修改 AppContext
中的属性,从而影响整个应用的行为。
总结
咱们今天一起扒了扒Vue 3 app
实例的Global API,包括 app.use
、app.component
、app.directive
、app.config
、app.mixin
,以及背后的 AppContext
。 希望通过今天的讲解,你能更深入地理解这些API的内部实现,并在实际开发中更加灵活地运用它们。
记住,app
实例是Vue 3应用的入口,通过它可以管理整个应用的状态和配置。 AppContext
则是 app
实例背后的核心上下文,包含了应用的所有全局信息。
希望这次的分享对你有所帮助! 咱们下次再见!