目录
[二、h() 函数:虚拟节点创建器](#二、h() 函数:虚拟节点创建器)
[1. 自定义渲染器](#1. 自定义渲染器)
[1. createElement(type)](#1. createElement(type))
[2. patchProp(el, key, prev, next)](#2. patchProp(el, key, prev, next))
[3. insert(child, parent)](#3. insert(child, parent))
[2. 动态内容更新](#2. 动态内容更新)
一、核心渲染三件套
在 Vue 的渲染系统中,有三个核心函数构成了渲染的基础:
import { createRenderer, h, render } from 'vue';
-
h()
- 虚拟节点创建器
-
createRenderer()
- 渲染器工厂
-
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() {
// 创建新VNode
const 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 渲染机制和实现自定义渲染解决方案的关键基础。