Vue3应用真正的启动过程

入口文件:packages/vue/src/index.ts

packages/vue/src/index.ts 并不是 Vue 框架的"启动器",而是一个精心设计的"API 导出器"和"构建入口" 它的核心任务是把所有分散在独立包(如 @vue/runtime-dom, @vue/reactivity)中的核心功能,重新组装并统一导出 ,最终打包成你熟悉的、可以通过 <script src="...">import Vue from 'vue' 引入的"完整版 Vue"。

这种设计带来了巨大的灵活性:

  1. 按需使用

    • 完整版 (import Vue from 'vue'):包含运行时和编译器,可以在浏览器中动态编译模板。
    • 仅运行时版 (import Vue from '@vue/runtime-dom'):体积小约30%,需配合构建工具(如Vite)预编译模板。
  2. 技术栈无关@vue/runtime-core 不依赖 DOM,这让 Vue 可以用于小程序、Canvas 等非 Web 环境。packages/vue 只是其 Web 版本的具体实现。

🚀 实际构建产物

当你运行 npm run build 时,构建工具(Rollup)会以这个 index.ts 为入口,生成最终分发文件:

  • vue.global.js - 用于浏览器的全局变量版本
  • vue.esm-browser.js - 用于浏览器的 ES 模块版本
  • vue.runtime.esm-bundler.js - 给构建工具用的仅运行时版本

所以,packages/vue/src/index.ts 并不是 Vue 应用的逻辑起点,而是 Vue 库本身的"包装出口" 。它的存在是为了:

  1. 技术整合:将分离的编译器与运行时连接起来。
  2. 构建入口:为打包工具提供唯一的起始点。
  3. 提供便利 :为开发者提供一个简单、统一的导入入口 (import Vue from 'vue')。

vue应用真正的启动过程,@vue/runtime-dom中的导出函数,这才是应用生命周期的开始

createApp是Vue3应用的真正的起点,将你的跟组件和用户代码连接到真实的DOM世界。 首先明确,你在 runtime-dom 中调用的 createApp实际上来自 runtime-coreruntime-dom 只是对它进行了类型扩展包装导出

核心代码位于 packages/runtime-dom/src/index.ts,但主要逻辑在 packages/runtime-core/src/apiCreateApp.ts 中。下面我将结合两者进行解析。

第一步:创建渲染器(基础设施)

createApp 被调用时,它背后依赖的核心是 createRenderer

typescript

javascript 复制代码
// 位于 runtime-dom/src/index.ts
// 1. 创建专用于DOM的渲染器,传入所有DOM操作方法(rendererOptions)
export const createApp = ((...args) => {
  // 2. ensureRenderer() 会调用 createRenderer(rendererOptions),
  //    生成一个具备 render 和 hydrate 方法的渲染器对象
  const app = ensureRenderer().createApp(...args)
  // ... 后续对 app.mount 的增强(见下文)
  return app
}) as CreateAppFunction<Element>
  • ensureRenderer() :获取或创建唯一的DOM渲染器。它是整个应用与DOM交互的发动机
  • createRenderer(rendererOptions) :这是关键。rendererOptions 就是之前定义的包含 patchPropinsertcreateElement 等几十个具体DOM操作方法的对象。createRenderer 函数(来自 runtime-core)接收这些具体方法,返回一个平台通用的渲染器。 这是依赖注入的经典实现:核心逻辑是通用的,具体实现由外部注入。
第二步:创建App实例(核心逻辑)

接着,调用渲染器的 .createApp() 方法,这是在 runtime-core 中定义的 createAppAPI 函数。

typescript

typescript 复制代码
// 位于 runtime-core/src/apiCreateApp.ts
export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  // 这个函数返回的就是我们最终使用的 createApp 函数
  return function createApp(rootComponent, rootProps = null) {
    // 1. 创建应用上下文对象 (context),这是全局配置和状态的集合
    const context = createAppContext()
    const installedPlugins = new Set() // 用于记录已安装插件,防止重复

    let isMounted = false // 标记应用是否已挂载

    // 2. 创建 app 对象(这就是我们得到的 app 实例)
    const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent, // 保存根组件对象
      _props: rootProps,
      _container: null, // 初始为null,mount后指向根DOM容器
      _context: context, // 关联上文创建的上下文

      // 3. 核心:mount 方法(初始版本,runtime-dom 会增强它)
      mount(rootContainer: HostElement, isHydrate?: boolean): any {
        if (isMounted) {
          warn(`App has already been mounted.`) // 开发环境警告
          return
        }
        // 3.1 标准化容器:支持字符串选择器(如'#app')或DOM元素
        const container = normalizeContainer(rootContainer)
        if (!container) return

        // 3.2 创建根组件的虚拟节点 (vnode)
        const vnode = createVNode(
          rootComponent as ConcreteComponent,
          rootProps
        )
        vnode.appContext = context // 将应用上下文关联到vnode

        // 3.3 核心渲染调用:将虚拟树转换为真实DOM
        if (isHydrate) {
          hydrate(vnode as VNode<Node, Element>, container)
        } else {
          render(vnode, container) // 这里调用的是 runtime-dom 提供的 render 函数
        }

        isMounted = true
        app._container = container // 记录挂载容器
        container.__vue_app__ = app // 在DOM元素上记录app实例,便于调试或HMR

        // 3.4 返回根组件实例的代理(方便少数需要直接操作实例的场景)
        return getExposeProxy(vnode.component!) || vnode.component!.proxy
      },

      // 4. 其他应用API
      use(plugin: Plugin, ...options: any[]) {
        // 插件安装逻辑,调用 plugin.install(app, ...options)
      },
      component(name: string, component?: Component): any {
        // 全局组件注册,存入 context.components
      },
      directive(name: string, directive?: Directive) {
        // 全局指令注册,存入 context.directives
      },
      // unmount, provide 等其他方法...
    })

    return app
  }
}

关键行解读:

  • const context = createAppContext():创建应用级别的共享上下文 。所有组件树中的组件都能访问到它内部的全局组件、指令、配置等。这是实现 app.componentapp.directive 全局注册的基石。
  • const vnode = createVNode(...):将你传入的根组件(可以是一个对象或一个组件定义)转换为初始的虚拟DOM节点。Vue 3 的所有渲染都围绕虚拟DOM进行。
  • render(vnode, container):这是点睛之笔 ,也是连接所有模块的枢纽。这个 render 函数就是第一步中 createRenderer 返回的核心渲染函数。它会启动整个patch(协调)算法 ,递归地将 vnode 树比对并渲染到真实的 container DOM节点中。
第三步:runtime-dommount 的增强(最后一公里)

runtime-dom 中,它拿到了 runtime-core 返回的基础 app 实例,但对 mount 方法做了关键增强,以支持更符合Web开发习惯的用法。

typescript

typescript 复制代码
// 位于 runtime-dom/src/index.ts (接第一步的代码)
const app = ensureRenderer().createApp(...args)

// 从 app 实例上获取原始的 mount 方法
const { mount } = app

// 重写(增强)mount 方法
app.mount = (containerOrSelector: Element | string): any => {
  // 1. 标准化容器(核心增强点):支持字符串选择器
  const container = normalizeContainer(containerOrSelector)
  if (!container) return

  // 2. 获取用户传入的根组件(可能是一个简单的模板对象)
  const component = app._component
  // 3. 如果根组件不是函数 & 没有render函数 & 有template,则将template编译为render函数
  if (!isFunction(component) && !component.render && !component.template) {
    component.template = container.innerHTML // 注意:这仅用于无构建步骤的CDN用法
  }

  // 4. 清除容器内的现有内容(在挂载前)
  container.innerHTML = ''

  // 5. 调用从 runtime-core 拿来的原始 mount 方法,执行真正的挂载
  const proxy = mount(container, false, container instanceof SVGElement)
  // ... 后续处理
  return proxy
}

为什么需要这个增强?

  1. 用户体验 :让 app.mount('#app') 这种写法成为可能,而不必每次都写 document.querySelector
  2. 支持纯HTML开发 :当你在没有构建步骤(如Webpack/Vite)的环境中,直接通过 <script> 标签引入Vue时,你可能需要在HTML中写模板。这段代码会检查根组件,如果它没有 render 函数,就会尝试将 template 选项(或容器内的HTML)在运行时编译render 函数。这就是"完整版"Vue(包含编译器)与"仅运行时版"的区别所在。

💎 总结

createApp 函数的主要工作可以概括为以下几步:

步骤 所在模块 核心工作 产出
1. 创建渲染器 runtime-dom 注入DOM API,创建平台专属渲染器。 具备 rendercreateApp 方法的渲染器对象。
2. 创建App实例 runtime-core 创建应用上下文和基础App对象,定义核心API。 一个包含 _context、基础 mount 方法的 app 实例。
3. 增强Mount runtime-dom mount 增加容器标准化和模板编译支持。 最终用户使用的、功能完整的 app.mount 方法。
4. 启动应用 用户调用 用户调用 app.mount,触发虚拟DOM渲染流程。 根组件被渲染成真实DOM,应用启动。

本质上,createApp 是一个高级工厂函数,它:

  1. 装配环境:将平台(浏览器)的具体操作与Vue的通用核心连接。
  2. 创建上下文:为整个组件树建立一个共享的配置和状态空间。
  3. 封装启动 :提供一个简单易用的 mount 方法,隐藏了虚拟DOM创建、渲染、挂载等复杂细节。
相关推荐
阿珊和她的猫14 分钟前
`require` 与 `import` 的区别剖析
前端·webpack
谎言西西里32 分钟前
零基础 Coze + 前端 Vue3 边玩边开发:宠物冰球运动员生成器
前端·coze
努力的小郑1 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程
GIS之路1 小时前
GDAL 实现数据空间查询
前端
OEC小胖胖1 小时前
01|从 Monorepo 到发布产物:React 仓库全景与构建链路
前端·react.js·前端框架
2501_944711432 小时前
构建 React Todo 应用:组件通信与状态管理的最佳实践
前端·javascript·react.js
困惑阿三2 小时前
2025 前端技术全景图:从“夯”到“拉”排行榜
前端·javascript·程序人生·react.js·vue·学习方法
苏瞳儿2 小时前
vue2与vue3的区别
前端·javascript·vue.js
weibkreuz3 小时前
收集表单数据@10
开发语言·前端·javascript
hboot4 小时前
别再被 TS 类型冲突折磨了!一文搞懂类型合并规则
前端·typescript