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. 提供应用级依赖注入

流程图

相关推荐
洋流2 分钟前
什么?还没弄懂关键字this?一篇文章带你速通
前端·javascript
晴殇i3 分钟前
for...in 循环的坑,别再用它遍历 JavaScript 数组了!
前端·javascript
海底火旺3 分钟前
寻找缺失的最小正整数:从暴力到最优的算法演进
javascript·算法·面试
littleplayer5 分钟前
iOS 单元测试详细讲解-DeepSeek
前端
littleplayer6 分钟前
iOS 单元测试与 UI 测试详解-DeepSeek
前端·单元测试·测试
夜熵9 分钟前
Vue中nextTick()用法
前端·面试
小桥风满袖9 分钟前
Three.js-硬要自学系列15 (圆弧顶点、几何体方法、曲线简介、圆、椭圆、样条曲线、贝塞尔曲线)
前端·css·three.js
洋流10 分钟前
JavaScript事件流机制详解:捕获、冒泡与阻止传播
前端·javascript
啊花是条龙10 分钟前
在 Angular 中使用 ECharts 并处理 xAxis 标签的点击事件
前端·angular.js
凌冰_15 分钟前
CSS3 基础(背景-文本效果)
前端·css·css3