Vue Vapor 应用初始化
VueVapor摒弃虚拟DOM和渲染器,但通过createApp与mount保持API兼容。初始化时创建组件实例并执行setup、render函数完成挂载。运行时架构更轻量,代码已重组至runtime-vapor目录,整体更高效。
一个有趣的对比:在 SolidJS 中,组件被视为代码组织方式,初始化后组件概念随即消失,因为其更新机制完全不依赖组件本身。而 Vue Vapor 则不同——它既要兼容 Vue3 的那套 API,又必须保留组件概念。

Vue3 应用初始化的核心逻辑
在 Vue3 应用初始化过程中,通常编写一个组件后调用:
复制代码const app = createApp(App)
app.mount("#app")
createApp 背后的逻辑是将组件转换为虚拟 DOM,再由渲染器将其渲染到页面。要理解这个流程,需先了解渲染器的工作原理。
渲染器通过 createRenderer 函数创建,该函数是工厂函数,返回渲染器对象。其基本结构如下:
复制代码// 创建渲染器
function createRenderer(options) {
// 渲染函数,主要是把一个虚拟 DOM 渲染到某一个元素节点上
function render(vnode, container) {
// 具体通过 patch 函数进行渲染
patch(null, vnode, container, null, null)
}
// 补丁函数
function patch(n1, n2, container) {
// 根据虚拟DOM 的类型不同进行不同的操作
}
// 返回渲染器对象
return {
createApp: createAppAPI(render)
}
}
渲染器的职责是将虚拟 DOM 转化为真实 DOM——包括元素的创建、删除、修改,以及属性的增删改。由于不同平台对 DOM 操作的 API 不同,createRenderer 允许根据不同平台传入对应操作函数。
日常使用 Vue3 时,默认依赖 runtime-dom 包,它封装了浏览器平台的 DOM 操作 API:
复制代码// 创建元素
function createElement(type) {
return document.createElement(type)
}
// 插入元素
function insert(child, parent, anchor) {
parent.insertBefore(child, anchor || null)
}
// 创建元素文本
function setElementText (el, text) {
el.textContent = text
}
// 创建渲染器
const renderer = createRenderer({
createElement,
insert,
setElementText
})
// 创建 Vue3 应用
export function createApp(...args) {
return renderer.createApp(...args)
}
可见,创建渲染器时将 createElement、insert、setElementText 等原生 DOM 操作封装成函数作为参数传入,返回的渲染器专门针对浏览器平台。
我们平时写的 const app = createApp(App),其 createApp 就是渲染器对象中的方法,它通过 createAppAPI 工厂函数创建。来看 createAppAPI 的具体实现:
复制代码// 创建 Vue3 应用实例对象
function createAppAPI(render) {
return function createApp(rootComponent) {
// 创建 Vue3 应用实例对象
const app = {
// 实例挂载方法
mount(rootContainer) {
// 创建根组件虚拟DOM
const vnode = createVNode(rootComponent)
// 把根组件的虚拟DOM 渲染到 #app 节点上
render(vnode, rootContainer)
}
}
return app
}
}
这段代码本质上是闭包:createAppAPI 缓存了不同渲染器中的 render 方法,然后返回 createApp 函数。我们调用的 createApp(App) 就是这个函数。返回的应用对象包含 mount 方法,挂载时先创建根组件的虚拟 DOM,再通过渲染器的 render 方法渲染到指定 DOM 节点(通常是 #app)。
在 Vue Vapor 中,情况则不同——由于没有虚拟 DOM,渲染器实例已不需要。但为了保持 API 兼容,Vue Vapor 的启动方式仍需与 Vue3 一致。
Vue Vapor 的应用初始化
为了让 Vue3 项目未来能平滑升级到 Vue Vapor,也需要支持 createApp(App).mount('#app') 这种调用方式。既然不再需要虚拟 DOM 和渲染器,直接从 createAppAPI 返回的 createApp 开始改造:
复制代码function createApp(rootComponent) {
// 创建 Vue Vapor 应用实例对象
const app = {
// 实例挂载方法
mount(rootContainer) {
// 把根组件的挂载到 #app 节点上
render(rootComponent, rootContainer)
}
}
return app
}
这样调用方式变成:
复制代码const root = document.getElementById('app')
- render(App, root)
+ const app = createApp(App)
+ app.mount(root)
当然,为支持 app.mount('#app') 这种字符串参数,需添加获取根元素的函数,在 render 方法中处理:
复制代码function normalizeContainer(container) {
return typeof container === 'string'
(document.querySelector(container))
: container
}
接着修改 render 方法:
复制代码function render(comp, container) {
const render = typeof comp === 'function' comp : comp.render
const block = render()
- insert(block, container)
+ insert(block, (container = normalizeContainer(container)))
}
这样,Vue Vapor 的应用初始化调用方式与 Vue3 完全一致:
复制代码const app = createApp(App)
app.mount('#app')
Vue Vapor 组件初始化流程
如上所述,SolidJS 中组件仅是代码组织方式,初始化后即消失。但 Vue Vapor 因需兼容 Vue3 API,仍需保留组件“概念”。
首先创建组件实例对象,保存状态信息,如指令、是否已挂载、生命周期钩子等:
复制代码export const createComponentInstance = (
component
) => {
const instance = {
block: null,
container: null, // set on mount
component
// TODO: registory of provides, appContext, lifecycles, ...
}
return instance
}
有了实例,需有挂载函数将其渲染到页面:
复制代码function mountComponent(
instance,
container
) {
instance.container = container
const render = typeof instance.component === 'function' instance.component : instance.component.render
const block = render()
insert(block, instance.container)
}
原来 render 函数的核心逻辑现在移至 mountComponent,render 函数相应调整:
复制代码function render(comp, container) {
- const render = typeof comp === 'function' comp : comp.render
- const block = render()
- insert(block, (container = normalizeContainer(container)))
+ const instance = createComponentInstance(comp)
+ mountComponent(instance, (container = normalizeContainer(container)))
}
之前测试使用函数组件,但 Vue3 中更常见的是状态组件——例如 script setup 编译后的对象。示例:
复制代码const App = {
setup() {
const count = ref(0)
return { count }
},
render(_ctx) {
// 生成创建 button 标签的函数
const _tmpl$ = template('')
// 真正进行创建模板内容的地方
const el = _tmpl$()
el.addEventListener('click', () => {
_ctx.count.value++
})
effect(() => {
el.textContent = _ctx.count.value
})
return el
}
}
实现此类状态组件的渲染,需先执行 setup 方法,将其结果作为参数传递给 render,再执行 render 获取最终 DOM。此逻辑在 mountComponent 中迭代即可:
复制代码function mountComponent(
instance,
container
) {
instance.container = container const { component } = instance
// 判断是状态组件还是函数组件
const setupFn =
typeof component === 'function' component : component.setup
// 获取 setup 方法的执行结果
const state = setupFn && setupFn()
// 执行 render 函数获取 DOM 结果
const block = instance.block = component.setup component.render(state) : state
// 挂载组件DOM元素到到父级元素上
insert(block, instance.container)
// 设置已经挂载的标记
instance.isMounted = true
// TODO: lifecycle hooks (mounted, ...)
// const { m } = instance
// m && invoke(m)
}
测试表明,状态组件也能正常渲染。
代码组织结构调整优化
截至目前,包含测试代码在内不足一百行,已清晰阐述 Vue Vapor 运行时基本原理。去除了虚拟 DOM,整个运行时架构远比之前轻量——这也是无虚拟 DOM 性能更优的重要原因。无需 diff 算法和对比,性能开销大幅降低。
为便于后续开发,需对代码组织架构进行重构。
首先在根目录新建 runtime-vapor 目录,存放 Vue Vapor 运行时代码。结构如下:
复制代码├── runtime-vapor
│ ├── src
│ │ ├── index.js // Vapor 运行时程序入口文件
| | ├── apiCreateApp.js // 存放 createApp API
| | ├── render.js // 渲染相关的
| | ├── component.js // 组件相关的
| | ├── template.js // 生成原生模板的
apiCreateApp.js 文件内容:
复制代码import { render } from "./render"
export function createApp(rootComponent) {
// 创建 Vue3 应用实例对象
const app = {
// 实例挂载方法
mount(rootContainer) {
// 把根组件的挂载到 #app 节点上
render(rootComponent, rootContainer)
}
}
return app
}
render.js 文件内容:
复制代码import { createComponentInstance } from './component'
export function render(comp, container) {
const instance = createComponentInstance(comp)
mountComponent(instance, (container = normalizeContainer(container)))
}function normalizeContainer(container) {
return typeof container === 'string'
(document.querySelector(container))
: container
}function mountComponent(
instance,
container
) {
instance.container = container const { component } = instance
// 判断是状态组件还是函数组件
const setupFn =
typeof component === 'function' component : component.setup
// 获取 setup 方法的执行结果
const state = setupFn && setupFn()
// 执行 render 函数获取 DOM 结果
const block = instance.block = component.setup component.render(state) : state
// 挂载组件DOM元素到到父级元素上
insert(block, instance.container)
// 设置已经挂载的标记
instance.isMounted = true
// TODO: lifecycle hooks (mounted, ...)
// const { m } = instance
// m && invoke(m)
}function insert(block, parent, anchor = null) {
parent.insertBefore(block, anchor)
}
component.js 文件内容:
复制代码let uid = 0
export const createComponentInstance = (
component
) => {
const instance = {
uid: uid++,
block: null,
container: null, // set on mount
component,
isMounted: false
// TODO: registory of provides, appContext, lifecycles, ...
}
return instance
}
template.js 文件内容:
复制代码export function template(html) {
let node
const create = () => {
const t = document.createElement("template")
t.innerHTML = html
return t.content.firstChild
}
const fn = () => (node || (node = create())).cloneNode(true)
return fn
}
index.js 作为入口文件,导出所有公共 API:
复制代码export { ref, effect } from '@vue/reactivity'
export { render } from './render'
export { template } from './template'
export { createApp } from './apiCreateApp'
接着将测试组件 App 放入 src/App.js:
复制代码import { ref, template, effect } from "../runtime-vapor/src"
const App = {
setup() {
const count = ref(0)
return { count }
},
render(_ctx) {
// 生成创建 button 标签的函数
const _tmpl$ = template('')
// 真正进行创建模板内容的地方
const el = _tmpl$()
el.addEventListener('click', () => {
_ctx.count.value++
})
effect(() => {
el.textContent = _ctx.count.value
})
return el
}
}export default App
最后,src/main.js 可写成与传统 Vue3 应用完全一致的形式:
复制代码import { createApp } from '../runtime-vapor/src'
import App from './App'const app = createApp(App)
app.mount('#app')
重构完成后的目录结构为:
复制代码├── runtime-vapor
│ ├── src
│ │ ├── index.js // Vapor 运行时程序入口文件
| | ├── apiCreateApp.js // 存放 createApp API
| | ├── render.js // 渲染相关的
| | ├── component.js // 组件相关的
| | ├── template.js // 生成原生模板的
├── src
│ ├── App.js // 测试组件
│ └── main.js // 应用入口文件
├── index.html
└── package.json
总结
本文从 Vue3 的渲染器和虚拟 DOM 出发,对比分析了 Vue Vapor 在应用初始化上的设计思路。
Vue Vapor 直接移除了虚拟 DOM 和渲染器层,将组件编译为原生 DOM 操作。因此 createApp 不再依赖 createRenderer,而是直接创建应用实例,通过 render 完成挂载。引入组件实例对象(createComponentInstance)和挂载函数(mountComponent)后,Vue Vapor 在兼容 Vue3 组件 API(如 setup、render)的同时,大幅简化了运行时代码结构。
去除 diff 算法和 patch 流程后,整体架构更加轻量,性能开销显著降低。代码组织上将运行时拆分为 apiCreateApp、render、component、template 等独立模块,职责清晰,便于后续扩展与维护。
这种设计既保证了 Vue3 项目未来可平滑升级,也为无虚拟 DOM 的高性能渲染奠定了基础,堪称 Vue 技术栈在编译时优化方向上迈出的重要一步。
