Vue.js createAppAPI 函数详解
概述
createAppAPI
是 Vue 3 核心库中的关键函数,用于创建应用实例。它被设计为一个高阶函数,接收平台特定的渲染函数,返回用于创建 Vue 应用的 createApp
函数。这种设计实现了 Vue 核心与平台渲染层的解耦,使 Vue 可以在不同环境(浏览器、服务器、原生平台等)中运行。
源码
typescript
export function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>,
hydrate?: RootHydrateFunction,
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
rootComponent = extend({}, rootComponent)
}
if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}
const context = createAppContext()
const installedPlugins = new WeakSet()
const pluginCleanupFns: Array<() => any> = []
let isMounted = false
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
get config() {
return context.config
},
set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`,
)
}
},
use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied to target app.`)
} else if (plugin && isFunction(plugin.install)) {
installedPlugins.add(plugin)
plugin.install(app, ...options)
} else if (isFunction(plugin)) {
installedPlugins.add(plugin)
plugin(app, ...options)
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`,
)
}
return app
},
mixin(mixin: ComponentOptions) {
if (__FEATURE_OPTIONS_API__) {
if (!context.mixins.includes(mixin)) {
context.mixins.push(mixin)
} else if (__DEV__) {
warn(
'Mixin has already been applied to target app' +
(mixin.name ? `: ${mixin.name}` : ''),
)
}
} else if (__DEV__) {
warn('Mixins are only available in builds supporting Options API')
}
return app
},
component(name: string, component?: Component): any {
if (__DEV__) {
validateComponentName(name, context.config)
}
if (!component) {
return context.components[name]
}
if (__DEV__ && context.components[name]) {
warn(`Component "${name}" has already been registered in target app.`)
}
context.components[name] = component
return app
},
directive(name: string, directive?: Directive) {
if (__DEV__) {
validateDirectiveName(name)
}
if (!directive) {
return context.directives[name] as any
}
if (__DEV__ && context.directives[name]) {
warn(`Directive "${name}" has already been registered in target app.`)
}
context.directives[name] = directive
return app
},
mount(
rootContainer: HostElement,
isHydrate?: boolean,
namespace?: boolean | ElementNamespace,
): any {
if (!isMounted) {
// #5571
if (__DEV__ && (rootContainer as any).__vue_app__) {
warn(
`There is already an app instance mounted on the host container.\n` +
` If you want to mount another app on the same host container,` +
` you need to unmount the previous app by calling \`app.unmount()\` first.`,
)
}
const vnode = app._ceVNode || createVNode(rootComponent, rootProps)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context
if (namespace === true) {
namespace = 'svg'
} else if (namespace === false) {
namespace = undefined
}
// HMR root reload
if (__DEV__) {
context.reload = () => {
// casting to ElementNamespace because TS doesn't guarantee type narrowing
// over function boundaries
render(
cloneVNode(vnode),
rootContainer,
namespace as ElementNamespace,
)
}
}
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, namespace)
}
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(rootContainer as any).__vue_app__ = app
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = vnode.component
devtoolsInitApp(app, version)
}
return getComponentPublicInstance(vnode.component!)
} else if (__DEV__) {
warn(
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. \`const createMyApp = () => createApp(App)\``,
)
}
},
onUnmount(cleanupFn: () => void) {
if (__DEV__ && typeof cleanupFn !== 'function') {
warn(
`Expected function as first argument to app.onUnmount(), ` +
`but got ${typeof cleanupFn}`,
)
}
pluginCleanupFns.push(cleanupFn)
},
unmount() {
if (isMounted) {
callWithAsyncErrorHandling(
pluginCleanupFns,
app._instance,
ErrorCodes.APP_UNMOUNT_CLEANUP,
)
render(null, app._container)
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = null
devtoolsUnmountApp(app)
}
delete app._container.__vue_app__
} else if (__DEV__) {
warn(`Cannot unmount an app that is not mounted.`)
}
},
provide(key, value) {
if (__DEV__ && (key as string | symbol) in context.provides) {
warn(
`App already provides property with key "${String(key)}". ` +
`It will be overwritten with the new value.`,
)
}
context.provides[key as string | symbol] = value
return app
},
runWithContext(fn) {
const lastApp = currentApp
currentApp = app
try {
return fn()
} finally {
currentApp = lastApp
}
},
})
if (__COMPAT__) {
installAppCompatProperties(app, context, render)
}
return app
}
}
函数签名
typescript
export function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement>
参数
render
: 平台特定的渲染函数,负责将虚拟 DOM 渲染为实际 DOMhydrate
: (可选) 用于服务端渲染水合(hydration)的函数
返回值
- 返回一个类型为
CreateAppFunction<HostElement>
的函数,用于创建 Vue 应用实例
createApp 函数详解
createAppAPI
返回的 createApp
函数是用户直接调用的 API,其实现包含以下几个部分:
参数处理
typescript
function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
rootComponent = extend({}, rootComponent)
}
if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}
// ...
}
- 处理根组件参数,确保它是对象形式
- 验证根组件 props 是对象类型,否则发出警告并置为 null
应用上下文初始化
typescript
const context = createAppContext()
const installedPlugins = new WeakSet()
const pluginCleanupFns: Array<() => any> = []
let isMounted = false
- 创建应用上下文,包含组件、指令、混入等注册表
- 使用 WeakSet 跟踪已安装的插件,防止重复安装
- 创建数组存储插件清理函数
- 初始化挂载状态标志
应用实例创建
typescript
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
// 其他属性和方法...
})
- 创建应用实例对象并同时赋值给
context.app
- 设置内部属性如唯一ID、根组件引用等
- 添加 Vue 版本信息
核心 API 实现
应用实例提供了丰富的 API,以下是主要方法:
配置访问
typescript
get config() {
return context.config
},
set config(v) {
if (__DEV__) {
warn(`app.config cannot be replaced. Modify individual options instead.`)
}
}
- 提供 config 访问器,获取应用配置
- 防止整体替换配置对象
插件安装 (use)
typescript
use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied to target app.`)
} else if (plugin && isFunction(plugin.install)) {
installedPlugins.add(plugin)
plugin.install(app, ...options)
} else if (isFunction(plugin)) {
installedPlugins.add(plugin)
plugin(app, ...options)
} else if (__DEV__) {
warn(`A plugin must either be a function or an object with an "install" function.`)
}
return app
}
- 支持对象式插件(带 install 方法)和函数式插件
- 使用 WeakSet 防止重复安装
- 返回 app 实例以支持链式调用
全局混入 (mixin)
typescript
mixin(mixin: ComponentOptions) {
if (__FEATURE_OPTIONS_API__) {
if (!context.mixins.includes(mixin)) {
context.mixins.push(mixin)
} else if (__DEV__) {
warn('Mixin has already been applied to target app' +
(mixin.name ? `: ${mixin.name}` : ''))
}
} else if (__DEV__) {
warn('Mixins are only available in builds supporting Options API')
}
return app
}
- 在支持 Options API 的构建中添加全局混入
- 避免重复添加相同混入
- 返回 app 实例支持链式调用
全局组件注册 (component)
typescript
component(name: string, component?: Component): any {
if (__DEV__) {
validateComponentName(name, context.config)
}
if (!component) {
return context.components[name]
}
if (__DEV__ && context.components[name]) {
warn(`Component "${name}" has already been registered in target app.`)
}
context.components[name] = component
return app
}
- 支持注册或获取全局组件
- 验证组件名称合法性
- 返回 app 实例或已注册组件
全局指令注册 (directive)
typescript
directive(name: string, directive?: Directive) {
if (__DEV__) {
validateDirectiveName(name)
}
if (!directive) {
return context.directives[name] as any
}
if (__DEV__ && context.directives[name]) {
warn(`Directive "${name}" has already been registered in target app.`)
}
context.directives[name] = directive
return app
}
- 类似组件注册,支持注册或获取全局指令
- 验证指令名称合法性
- 返回 app 实例或已注册指令
应用挂载 (mount)
typescript
mount(
rootContainer: HostElement,
isHydrate?: boolean,
namespace?: boolean | ElementNamespace,
): any {
if (!isMounted) {
// 挂载逻辑...
const vnode = app._ceVNode || createVNode(rootComponent, rootProps)
vnode.appContext = context
// 处理命名空间
if (namespace === true) {
namespace = 'svg'
} else if (namespace === false) {
namespace = undefined
}
// 开发环境热更新支持
if (__DEV__) {
context.reload = () => { /* ... */ }
}
// 渲染或水合
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, namespace)
}
// 更新状态
isMounted = true
app._container = rootContainer
;(rootContainer as any).__vue_app__ = app
// 开发工具集成
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = vnode.component
devtoolsInitApp(app, version)
}
return getComponentPublicInstance(vnode.component!)
} else if (__DEV__) {
warn(/* 警告重复挂载 */)
}
}
- 检查是否已挂载,防止重复挂载
- 创建根组件的虚拟节点并设置上下文
- 支持 SVG 等特殊命名空间
- 提供开发环境热更新支持
- 根据参数选择渲染方式(SSR水合或普通渲染)
- 更新挂载状态和引用
- 设置开发工具集成
- 返回根组件的公共实例
卸载回调注册 (onUnmount)
typescript
onUnmount(cleanupFn: () => void) {
if (__DEV__ && typeof cleanupFn !== 'function') {
warn(`Expected function as first argument to app.onUnmount(), but got ${typeof cleanupFn}`)
}
pluginCleanupFns.push(cleanupFn)
}
- 注册应用卸载时执行的清理函数
- 开发环境下验证参数类型
应用卸载 (unmount)
typescript
unmount() {
if (isMounted) {
callWithAsyncErrorHandling(
pluginCleanupFns,
app._instance,
ErrorCodes.APP_UNMOUNT_CLEANUP
)
render(null, app._container)
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = null
devtoolsUnmountApp(app)
}
delete app._container.__vue_app__
} else if (__DEV__) {
warn(`Cannot unmount an app that is not mounted.`)
}
}
- 执行所有注册的清理函数
- 渲染 null 清空容器内容
- 清理组件实例和开发工具集成
- 移除容器上的应用引用
依赖注入 (provide)
typescript
provide(key, value) {
if (__DEV__ && (key as string | symbol) in context.provides) {
warn(`App already provides property with key "${String(key)}". It will be overwritten with the new value.`)
}
context.provides[key as string | symbol] = value
return app
}
- 在应用级别注册可注入的依赖
- 开发环境下警告键名冲突
- 返回 app 实例支持链式调用
上下文运行 (runWithContext)
typescript
runWithContext(fn) {
const lastApp = currentApp
currentApp = app
try {
return fn()
} finally {
currentApp = lastApp
}
}
- 在应用上下文中执行函数
- 临时设置全局当前应用
- 确保执行后恢复原全局上下文
- 支持在函数中使用 inject 函数访问应用提供的依赖
兼容处理与返回
typescript
if (__COMPAT__) {
installAppCompatProperties(app, context, render)
}
return app
- 在兼容模式下添加 Vue 2 兼容性属性
- 返回完整配置的应用实例
应用场景
createAppAPI
函数是 Vue 3 应用初始化的基础,用于:
- 创建独立的 Vue 应用实例
- 注册全局组件、指令和插件
- 配置应用级别选项
- 管理应用生命周期(挂载和卸载)
- 提供应用级依赖注入
流程图
