Vue3API解读-createApp()

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 渲染为实际 DOM
  • hydrate: (可选) 用于服务端渲染水合(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 应用初始化的基础,用于:

  1. 创建独立的 Vue 应用实例
  2. 注册全局组件、指令和插件
  3. 配置应用级别选项
  4. 管理应用生命周期(挂载和卸载)
  5. 提供应用级依赖注入

流程图

相关推荐
加班是不可能的,除非双倍日工资12 分钟前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi1 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip1 小时前
vite和webpack打包结构控制
前端·javascript
excel1 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国2 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼2 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy2 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT2 小时前
promise & async await总结
前端
Jerry说前后端2 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化
画个太阳作晴天2 小时前
A12预装app
linux·服务器·前端