核心在于解耦“描述 UI”和“渲染 UI”:虚拟 DOM 作为跨平台中间语言,以轻量 JavaScript 对象树(VNode)统一描述界面;不同平台渲染器(如 Web 的 DOM API、Canvas 的绘图指令)负责将 VNode 翻译为对应环境操作,共用响应式系统与 diff 算法,仅在 patch 阶段分叉实现。

核心在于解耦“描述 UI”和“渲染 UI”两个环节:虚拟 DOM 负责统一描述界面结构,而不同平台的渲染器(Renderer)负责把这份描述翻译成对应环境能理解的操作。
虚拟 DOM 是跨平台的中间语言
它不是浏览器专属,而是一个轻量、纯 JavaScript 的对象树(VNode),只保留关键信息:节点类型(如 div、text)、属性(id、style)、子节点列表。这套结构不依赖 DOM API,也不绑定 HTML 标签语义,因此可以被任意后端解释。
比如一个按钮组件,在 Web 中可能生成 ;在 Canvas 中,它不会创建真实 DOM 元素,而是被渲染器理解为一条绘制指令:在 (100, 50) 位置画一个带圆角、蓝色填充、居中文字的矩形区域。
平台渲染器接管最终输出
Vue 的 runtime-core 提供了通用的响应式系统和 diff 算法,但它不碰任何平台 API。真正干活的是你传入的渲染器配置:
- Web 渲染器:调用 document.createElement、el.setAttribute、el.appendChild 等原生 DOM 方法
- Canvas 渲染器:不操作 DOM,而是维护一个画布上下文(ctx),把每个 VNode 映射为 ctx.fillRect、ctx.fillText、ctx.strokeRect 等绘图调用
- 两者共用同一套虚拟节点更新逻辑:当数据变化时,diff 算法仍能比出哪些节点新增、删除或属性变更,只是后续“打补丁”的方式完全不同
Canvas 渲染需额外处理的关键点
DOM 天然支持事件冒泡、布局流、CSS 盒模型,Canvas 没有这些。所以 Canvas 渲染器必须自行补足:
- 坐标与布局:需实现简易布局引擎(如 Flex-like 计算),把 width/height/margin/padding 转为绝对像素坐标
- 事件映射:监听 canvas 的 click/mousemove,再根据鼠标位置反查落在哪个虚拟节点上,手动触发 onClick 等回调
- 脏区重绘:不重绘整块画布,而是记录变更节点的边界矩形(dirty rect),仅刷新受影响区域,避免性能浪费
一套代码如何同时跑在两个平台
关键不在“写一次”,而在“编译/运行时可切换”:
- 组件代码用标准 Vue 语法(template 或 setup),不直接调用 document 或 ctx
- 构建时,通过不同入口文件加载不同渲染器:Web 版 import { createApp } from 'vue';Canvas 版 import { createCanvasApp } from './renderer/canvas'
- 底层共享同一套响应式系统和虚拟节点定义,仅在 patch(打补丁)阶段分叉:一个走 DOM API,一个走 Canvas API


















