runtime-dom记录备忘

packages/runtime-dom/src/index.ts 的主要作用

  • 首先明确的是,在runtime-dom中调用的createApp,实际上是来自runtime-core。runtime-dom只是对它进行了类型扩展和包装导出;
  • 第一步:创建渲染器
    • 当createApp被调用时,他背后依赖的核心是createRenderer
ts 复制代码
// 位于 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>
  1. ensureRenderer():获取或创建唯一的DOM渲染器,他是整个应用与DOM交互的发动机。
  2. createRenderer(rendererOptions):这是关键。rendererOptions就是之前定义的包含patchProp、insert,createElement等几十个具体DOM操作方法的对象。createRenderer函数(来自runtime-core)接收这些具体方法,返回一个平台通用的渲染器。这是依赖注入的经典实现:核心逻辑是通用的,具体实现由外部注入。
  • 第二步创建App实例(核心逻辑) 接着,调用渲染器的.createApp方法,这是在runtime-core中定义的createAppApi函数
ts 复制代码
// 位于 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
  }
}

关键行解读:

  1. const context = createAppContext():创建应用级别的共享上下文。所有组件树中的组件都能访问到它内部的全局组件、指令、配置等。这是app.component、app.directive全局注册的基石。
  2. const vnode=createVNode(...)将你传入的跟组件(可以是一个对象或者一个组件定义)转换为初始的虚拟DOM节点。Vue3的所有渲染都围绕虚拟DOM进行
  3. render(vnode,container):是连接所有模块的枢纽。这个render函数第一步中createRender返回的核心渲染函数。它会启动整个patch(协调)算法,递归地将vnode树比对并渲染到真实的containerDOM节点中。
  • 第三步:runtime-dom对mount的增强 在 runtime-dom 中,它拿到了 runtime-core 返回的基础 app 实例,但对 mount 方法做了关键增强,以支持更符合Web开发习惯的用法。
ts 复制代码
// 位于 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
}

为什么需要这个增强? 用户体验:让app.mount('#app')这种写法成为可能,而不必每次都写documnet.querySelector。 支持纯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创建、渲染、挂载等复杂细节
相关推荐
wearegogog1232 小时前
基于 MATLAB 的卡尔曼滤波器实现,用于消除噪声并估算信号
前端·算法·matlab
Drawing stars2 小时前
JAVA后端 前端 大模型应用 学习路线
java·前端·学习
品克缤2 小时前
Element UI MessageBox 增加第三个按钮(DOM Hack 方案)
前端·javascript·vue.js
小二·2 小时前
Python Web 开发进阶实战:性能压测与调优 —— Locust + Prometheus + Grafana 构建高并发可观测系统
前端·python·prometheus
小沐°2 小时前
vue-设置不同环境的打包和运行
前端·javascript·vue.js
qq_419854053 小时前
CSS动效
前端·javascript·css
烛阴3 小时前
3D字体TextGeometry
前端·webgl·three.js
桜吹雪3 小时前
markstream-vue实战踩坑笔记
前端
C_心欲无痕4 小时前
nginx - 实现域名跳转的几种方式
运维·前端·nginx
花哥码天下4 小时前
恢复网站console.log的脚本
前端·javascript·vue.js