Vue 渲染三剑客:createRenderer、h 和 render 详解
目录
一、核心渲染三件套
二、h() 函数:虚拟节点创建器
基本用法
参数详解
虚拟节点(VNode)结构
为什么需要虚拟节点?
三、createRenderer():渲染器工厂
渲染器返回值
自定义渲染器示例
四、render():渲染执行函数
方法签名
工作流程
核心算法伪代码
五、三者的协同工作
完整流程图示
六、实际应用场景
1. 自定义渲染器
1. createElement(type)
2. patchProp(el, key, prev, next)
3. insert(child, parent)
整体执行流程
2. 动态内容更新
七、总结:渲染三剑客的关系
一、核心渲染三件套
在 Vue 的渲染系统中,有三个核心函数构成了渲染的基础:
import { createRenderer, h, render } from 'vue';
- 1.
h()
- 虚拟节点创建器 - 2.
createRenderer()
- 渲染器工厂 - 3.
render()
- 渲染执行函数
这三个函数协同工作,将声明式的组件描述转化为实际的 DOM 操作。下面我们逐一深入解析。
二、h() 函数:虚拟节点创建器
h()
是 Vue 中创建虚拟节点(Virtual DOM Node)的核心函数,名称源自 "hyperscript",意为 "生成 HTML 结构的脚本"
// 创建简单元素
const vnode = h('div', 'Hello World');// 创建带属性的元素
const vnode = h('button', {class: 'btn',onClick: () => console.log('Clicked!')
}, 'Submit');// 创建嵌套结构
const vnode = h('div', [h('h1', 'Main Title'),h('p', 'Content paragraph')
]);
基本用法
// 创建简单元素
const vnode = h('div', 'Hello World');// 创建带属性的元素
const vnode = h('button', {class: 'btn',onClick: () => console.log('Clicked!')
}, 'Submit');// 创建嵌套结构
const vnode = h('div', [h('h1', 'Main Title'),h('p', 'Content paragraph')
]);
参数详解
h()
函数有三种主要调用形式:
// 1. 仅标签类型
h('div')// 2. 标签 + 属性/子元素
h('div', { id: 'app' })
h('div', 'Hello')// 3. 标签 + 属性 + 子元素
h('div', { class: 'container' }, [h('span', 'Item 1'),h('span', 'Item 2')
])
虚拟节点(VNode)结构
h()
返回的虚拟节点对象包含渲染所需的所有信息:
{type: 'div', // HTML标签或组件对象props: { // 属性对象id: 'app',class: 'container'},children: [ // 子节点数组{ type: 'p', children: 'Text content' },// ...其他子节点],el: null, // 对应的真实DOM(渲染后填充)key: undefined, // 用于优化的key// ...其他内部属性
}
为什么需要虚拟节点?
-
跨平台能力:VNode 是平台无关的抽象表示
-
高效更新:通过比较新旧 VNode 树,最小化 DOM 操作
-
灵活的组件模型:支持函数式组件、异步组件等高级特性
-
服务端渲染:可在 Node.js 环境中生成 HTML 字符串
三、createRenderer():渲染器工厂
createRenderer()
是创建自定义渲染器的工厂函数,它接收一个包含平台特定操作的配置对象。
const renderer = createRenderer({// 创建元素createElement(tag) {return document.createElement(tag);},// 设置元素文本setElementText(el, text) {el.textContent = text;},// 插入元素insert(child, parent, anchor = null) {parent.insertBefore(child, anchor);},// 属性比对更新patchProp(el, key, prevValue, nextValue) {// 处理class/style/event/attributes},// 其他操作...
});
渲染器返回值
createRenderer()
返回一个包含关键方法的对象:
const {render, // 渲染方法hydrate, // 服务端渲染激活方法createApp // 创建应用实例
} = renderer;
自定义渲染器示例
实现一个控制台渲染器:
const consoleRenderer = createRenderer({createElement(tag) {return { tag };},insert(child, parent) {console.log(`添加 ${child.tag} 到 ${parent.tag}`);},setElementText(el, text) {console.log(`设置 ${el.tag} 文本: "${text}"`);el.text = text;}
});const vnode = h('div', {}, [h('p', {}, 'Hello Console!')]);
consoleRenderer.render(vnode, { tag: 'root' });
输出:
添加 p 到 div
设置 p 文本: "Hello Console!"
添加 div 到 root
四、render():渲染执行函数
render()
是实际执行渲染工作的方法,它将虚拟 DOM 转换为真实 DOM。
方法签名
function render(vnode: VNode | null, container: HostElement): void;
工作流程
首次渲染:
render(h('div', 'Hello'), document.getElementById('app'));
-
创建
<div>
元素 -
设置文本内容为 "Hello"
-
将元素插入到 app 容器中
更新渲染:
// 第一次渲染
render(h('div', 'Initial'), container);// 更新内容
render(h('div', 'Updated'), container);
-
比较新旧 VNode
-
仅更新变化的文本内容
卸载组件:
// 渲染空内容
render(null, container);
核心算法伪代码
function render(vnode, container) {if (vnode === null) {// 卸载逻辑if (container._vnode) {unmount(container._vnode);}} else {// 挂载或更新patch(container._vnode || null, vnode, container);}// 存储当前VNode引用container._vnode = vnode;
}function patch(oldVNode, newVNode, container) {if (oldVNode === null) {// 首次挂载mount(newVNode, container);} else {// 更新逻辑if (shouldUpdate(oldVNode, newVNode)) {// 执行更新updateElement(oldVNode, newVNode);} else {// 完全替换unmount(oldVNode);mount(newVNode, container);}}
}
五、三者的协同工作
理解这三个函数如何协同工作至关重要:
设计阶段:
// 1. 创建渲染器(平台相关)
const renderer = createRenderer({ /* DOM操作 */ });// 2. 获取渲染函数
const { render } = renderer;
声明阶段:
// 3. 使用h()创建虚拟节点
const vnode = h('div', { class: 'app' }, [h('h1', 'Hello Vue'),h('p', 'This is virtual DOM')
]);
执行阶段:
// 4. 使用render()执行渲染
render(vnode, document.getElementById('app'));
完整流程图示
h() 创建↓
虚拟节点树 (VNode Tree)↓
render() 处理↓
createRenderer 配置↓
真实 DOM 操作↓
浏览器显示
六、实际应用场景
1. 自定义渲染器
1. createElement(type)
-
作用:创建虚拟 DOM 元素的基础对象
-
参数:
-
type
:元素类型(如'circle'
)
-
-
返回值:返回一个包含
type
属性的基础对象(如{ type: 'circle' }
) -
执行时机:在调用
h()
函数创建虚拟节点时触发 -
示例:
h('circle', ...) // 内部调用 createElement('circle')
2. patchProp(el, key, prev, next)
-
作用:设置/更新虚拟 DOM 元素的属性
-
参数:
-
el
:虚拟元素对象(由createElement
创建) -
key
:属性名(如'position'
) -
prev
:旧属性值(首次创建时为null
) -
next
:新属性值
-
-
执行时机:
-
初始化时:为每个属性设置初始值
-
更新时:当属性变化时更新值
-
// 将 position 属性添加到 circle 元素
patchProp(circleObj, 'position', null, [100, 100])
3. insert(child, parent)
-
作用:将子元素插入父容器,并触发实际绘制
-
参数:
-
child
:子虚拟元素(如圆形对象) -
parent
:父容器(此处未直接使用)
-
-
执行时机:当虚拟 DOM 的子节点被挂载到父节点时
-
关键逻辑:
if (child.type === 'circle') {draw(child); // 调用绘制函数
}
整体执行流程
-
创建虚拟 DOM:
const scene = h('canvas', {}, [h('circle', { position: [100, 100], radius: 50, color: 'blue' }),h('circle', { position: [200, 150], radius: 30, color: 'red' }) ]);
-
渲染过程:
-
遍历虚拟 DOM 树
-
对每个
circle
元素:-
createElement('circle')
→ 创建基础对象{ type: 'circle' }
-
patchProp()
→ 添加position/radius/color
属性 -
insert()
→ 触发draw()
绘制
-
-
draw()
→ 使用 Canvas API 绘制圆形
-
const canvasElement = document.getElementById('canvas');const ctx = canvasElement.getContext('2d');const canvasRenderer = createRenderer({createElement(type) {return { type };},patchProp(el, key, prev, next) {el[key] = next;},insert(child, parent) {if (child.type === 'circle') {draw(child);}},});function draw(element) {const [x, y] = element.position || [0, 0];const radius = element.radius || 10;const color = element.color || 'black';ctx.beginPath();ctx.arc(x, y, radius, 0, Math.PI * 2);ctx.fillStyle = color;ctx.fill();}// 创建Canvas场景const scene = h('canvas', {}, [h('circle', { position: [100, 100], radius: 50, color: 'blue' }),h('circle', { position: [200, 150], radius: 30, color: 'red' })]);canvasRenderer.render(scene, canvasElement);;
2. 动态内容更新
let count = 0;
const container = document.getElementById('app');function update() {// 创建新VNodeconst vnode = h('div', [h('h1', `Count: ${count}`),h('button', { onClick: () => {count++;update(); // 更新视图}}, 'Increment')]);// 渲染更新render(vnode, container);
}// 初始渲染
update();
七、总结:渲染三剑客的关系
函数 | 角色 | 输入 | 输出 |
---|---|---|---|
h() | 声明式构建界面 | 组件描述 | 虚拟节点(VNode) |
createRenderer() | 创建特定平台的渲染能力 | 平台DOM操作实现 | 渲染器对象 |
render() | 执行渲染过程 | VNode + DOM容器 | 实际UI更新 |
关键理解要点:
-
h()
是声明式的:描述"应该是什么样子" -
createRenderer()
是平台适配层:决定"如何实现渲染" -
render()
是执行引擎:处理"何时以及怎样更新"
这种分层架构使得 Vue 能够:
-
保持核心逻辑与平台无关
-
轻松实现跨平台渲染
-
提供高效的更新机制
-
保持开发者友好的声明式 API
理解这三个核心函数的工作原理,是深入掌握 Vue 渲染机制和实现自定义渲染解决方案的关键基础。