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创建、渲染、挂载等复杂细节
相关推荐
小猪努力学前端32 分钟前
基于PixiJS的小游戏广告开发
前端·webgl·游戏开发
哆啦A梦158837 分钟前
62 对接支付宝沙箱
前端·javascript·vue.js·node.js
用户8168694747251 小时前
Lane 优先级模型与时间切片调度
前端·react.js
虎头金猫1 小时前
MateChat赋能电商行业智能导购:基于DevUI的技术实践
前端·前端框架·aigc·ai编程·ai写作·华为snap·devui
LiuMingXin1 小时前
CESIUM JS 学习笔记 (持续更新)
前端·cesium
豆苗学前端1 小时前
面试复盘:谈谈你对 原型、原型链、构造函数、实例、继承的理解
前端·javascript·面试
Crystal3281 小时前
Git 基础:生成版本、撤消操作、版本重置、忽略文件
前端·git·github
lichenyang4531 小时前
React 组件通讯全案例解析:从 Context 到 Ref 的实战应用
前端
姓王者1 小时前
chen-er 专为Chen式ER图打造的npm包
前端·javascript