19.Vue Vapor 的实现原理原来这么简单

前言

我们了解了 SolidJS 的基础实现原理之后,再去探索实现 Vue Vapor 就比较容易了,在 Vue3 中数据响应式部分是独立成了一个库,我们可以直接使用 @vue/reactivity 这个库响应式,然后再去实现运行时部分。为了方便开发,我们这里先搭建一个 Vite 的开发环境。

手动搭建一个 vite 开发环境

首先创建一个项目目录 vue-vapor-cobyte-test,然后进到目录里面进行 npm 项目的初始化:

csharp 复制代码
npm init -y

然后安装 vite@vue/reactivity

sql 复制代码
pnpm add vite @vue/reactivity 

然后修改 package.json 文件的 scripts 选项设置 vite 的启动命令:

diff 复制代码
  "scripts": {
+    "dev": "vite"
  },

在开发期间 Vite 是一个服务器,所以我们需要创建 vite 的启动入口文件 index.html,内容如下:

html 复制代码
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue Vapor</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="./src/main.js"></script>
  </body>
</html>

然后我们创建一个 src 目录,在 src 根目录创建一个 main.js 文件,内容如下:

javascript 复制代码
const root = document.getElementById('app')
root.textContent = '掘金签约作者:Cobyte'

到此我们基础的 Vite 开发环境就搭建完成了,我们通过命令来启动它:

复制代码
pnpm dev

页面渲染结果如下:

我们可以看到已经成功启动了我们的项目,接下来我们就在这个 Vite 的开发环境中探索 Vue Vapor 的实现原理吧。

Vue Vapor 的核心原理实现

任何复杂的框架,只有通过最简单的代码去实现它的基本运行逻辑,才算是真正掌握了它实现原理。所谓高端的技术往往采用最原始的编程方式,还有那一句名言:Atwood 定律:Any application that can be written in JavaScript, will eventually be written in JavaScript. (翻译过来即是:凡是能用 JavaScript 写出来的,最终都会用 JavaScript 写出来)。

我们通过前面对 SolidJS 的实现原理的探索,在此基础之上,我们再去实现 Vue Vapor 则比较容易了,就像我们了解了 Vue 的数据响应式原理之后,再去了解 Mobx 的实现原理也变得简单一样,因为从宏观角度来看它们背后的实现原理是相似的,甚至可以说是一样的。

首先我们可以把 SolidJS 中的模板生成函数直接拿过来。

javascript 复制代码
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 
}

我们之前写的 SolidJS 的组件如下:

接着我们像 SolidJS 那样写一个组件,同时响应式变量我们则通过 @vue/reactivity 来生成:

javascript 复制代码
// 生成创建 button 标签的函数
const _tmpl$ = template('<button></button>')
const App = () => {
    const count = ref(0)
    // 真正进行创建模板内容的地方
    const el = _tmpl$()
    el.addEventListener('click', () => {
        count.value ++
    })
    insert(el, count)
    return el
}

接着我们把 render 和 insert 方法也拿过来,我们之前写的如下:

现在我们需要对 insert 方法进行改造一下,使用 @vue/reactivity 的 effect 函数实现依赖收集。

javascript 复制代码
function render(component, parent) {
    parent.appendChild(component)
}

function insert(parent, accessor) {
    effect(() => {
        parent.textContent = accessor.value
    })
}

这样我们就可以在实现无虚拟DOM的应用了,并且可以自动更新。

javascript 复制代码
const root = document.getElementById('app')
render(App(), root)

渲染结果如下:

完整代码如下:

javascript 复制代码
import { ref, effect } from '@vue/reactivity'
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 
}

function render(component, parent) {
    parent.appendChild(component)
}

function insert(parent, accessor) {
    effect(() => {
        parent.textContent = accessor.value
    })
}

// 生成创建 button 标签的函数
const _tmpl$ = template('<button></button>')
const App = () => {
    const count = ref(0)
    // 真正进行创建模板内容的地方
    const el = _tmpl$()
    el.addEventListener('click', () => {
        count.value++
    })
    insert(el, count)
    return el
}
const root = document.getElementById('app')
render(App(), root)

Vue Vapor 设计原理

我们知道 Vue Vapor 是在 Vue3 的设计基础上实现的一个无虚拟 DOM 的版本,那么也就是说它应该是需要兼容目前 Vue3 的语法的,不然就又是一个新框架了。我们在上述实现的小 demo 是在 SolidJS 加 Vue3 的响应式库实现的,总体风格是 SolidJS 的设计风格,很明显上述的实现是不兼容目前 Vue3 的语法的,所以我们还需要进行迭代我们的功能。

那么我们要兼容 Vue3 的语法,首先需要修改的就是我们的 render 函数的调用方式,我们目前所实现的 App 组件是一个函数组件,在我们原来 Vue3 中,render 函数第一个参数则是一个对象或者一个函数,因为 Vue3 中的组件在数据类型上进行区分,就只有两种,一种是虚拟DOM 对象(也就是我们平时所写的 .vue 文件最后会被编译成一个对象),一种则是函数(也就是组件函数)。

调用方式修改如下:

diff 复制代码
- render(App(), root)
+ render(App, root)

接着我们修改 render 函数:

javascript 复制代码
function render(comp, container) {
    const render = typeof comp === 'function' ? comp : comp.render
    const block = render()
    insert(block, container)
}

如果是组件函数则组件函数本身就是 render 渲染函数,否则就是组件对象上的 render 函数。得到 render 渲染函数后,执行渲染函数得到真实 DOM,然后挂载到对应的根节点上。

insert 函数的修改则如下:

javascript 复制代码
function insert(block, parent, anchor = null) {
    parent.insertBefore(block, anchor)
}

在 SolidJS 中,依赖收集的副作用函数是放在 insert 函数中,我们也可以放在编译后的组件函数中,所以我们的对编译后的组件修改如下:

javascript 复制代码
const App = () => {
    // 生成创建 button 标签的函数
    const _tmpl$ = template('<button></button>')  
    const count = ref(0)
    // 真正进行创建模板内容的地方
    const el = _tmpl$()
    el.addEventListener('click', () => {
        count.value ++
    })
    effect(() => {
        el.textContent = count.value
    })
    return el
}

渲染结果如下:

完整代码如下:

javascript 复制代码
import { ref, effect } from '@vue/reactivity'
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 
}

function render(comp, container) {
    const render = typeof comp === 'function' ? comp : comp.render
    const block = render()
    insert(block, container)
}

function insert(block, parent, anchor) {
    parent.insertBefore(block, anchor)
}

const App = () => {
    const _tmpl$ = template('<button></button>')
    const count = ref(0)
    const el = _tmpl$()
    el.addEventListener('click', () => {
        count.value++
    })
    effect(() => {
        el.textContent = count.value
    })
    return el
}
const root = document.getElementById('app')
render(App, root)

我们可以看到 Vue Vapor 核心实现的代码 50 行都不到。

总结

本文从零开始搭建了一个基于 Vite 的开发环境,并借助 @vue/reactivity 响应式库,成功实现了一个极简版的 Vue Vapor 运行时核心。通过对比 SolidJS 的实现思路,我们看到了无虚拟 DOM 框架的基本模式:模板编译生成 DOM 元素、利用响应式系统的 effect 自动追踪依赖并直接更新 DOM 内容。整个核心代码仅 50 行左右,却完整展现了 Vue Vapor 的设计精髓------抛弃虚拟 DOM,回归原生 DOM 操作,同时保持与 Vue3 组件语法的兼容性。

关键实现步骤包括:

  1. 使用 template 函数静态克隆 DOM 节点,提升创建效率;
  2. 通过 ref 定义响应式状态,利用 effect 建立数据与视图的绑定;
  3. 组件函数直接返回真实 DOM 节点,由 render 函数完成挂载;
  4. 事件监听直接绑定在原生元素上,无需额外代理。

这一实现不仅验证了 "任何能用 JavaScript 写出的最终都会用 JavaScript 写出" 的理念,也为深入理解 Vue Vapor 后续的指令系统、组件化、调度更新等进阶特性奠定了扎实的基础。无虚拟 DOM 的路径在性能敏感场景下具有天然优势,Vue Vapor 正是 Vue 生态对这一方向的积极探索。

我是程序员Cobyte,现在已转向研究 AI Agent,欢迎添加 v: icobyte,学习交流 AI Agent 应用开发。

相关推荐
JackieDYH1 小时前
uniapp vue3 常用的生命周期和作用使用时机
javascript·vue.js·uni-app
郝学胜-神的一滴1 小时前
中级OpenGL教程 009:用环境光告别模型死黑
前端·c++·unity·godot·图形渲染·opengl·unreal
半岛盒子1 小时前
AI Coding方案与事件流(前端)
前端
星栈1 小时前
Makepad 应用如何读文件、调接口、保存数据
前端·rust
qq_466302451 小时前
office 2021 下载安装激活
前端
新新学长搞科研1 小时前
【广东省博促会主办】2026年第七届先进材料与智能制造国际学术会议(ICAMIM 2026)
大数据·前端·数据库·人工智能·物联网
铁皮饭盒1 小时前
用bunjs代码讲解XSS/CSRF/SQL注入/DDos等10种前后端安全防护
前端·后端
hhb_6181 小时前
TypeScript泛型实战:企业级请求封装全解析
javascript·ubuntu·typescript
琹箐1 小时前
chrome 插件下载安装;Manifest file is missing or unreadable
前端·chrome