基础实现
约 856 字大约 3 分钟
2026-04-09
渲染函数的基本设计
在 Vue 中,渲染函数负责将虚拟 DOM 转换为真实 DOM,并将其渲染到页面中。所以对于渲染函数来说,它现在值少需要两个参数:
- 虚拟节点
vnode - 真实的挂载容器
container
function render(vnode, container) {}接下来需要考虑的是 render 函数在不同场景下应该如何处理
在调用 render 函数时,通常会有三种情况:
- 如果容器中 没有 旧的
vnode,则说明这是首次渲染,需要将新的内容渲染出来,也就是挂载操作,此时可以调用mountElement函数完成挂载 - 如果容器中 已经存在 旧的
vnode,则说明这是一次更新,需要比较新旧vnode之间的差异,并根据差异更新真实 DOM,此时可以调用patch函数完成更新 - 如果本次传入的
vnode不存在,但容器中仍然保存着旧的vnode,则说明需要移除旧内容,也就是卸载操作,此时可以调用unmount函数完成卸载
基本逻辑
function render(vnode, container) {
// case 1
if (vnode) {
patch(container._vnode, vnode, container);
}
// case 2
else {
// case 3
// 此时表明旧的虚拟 DOM 存在,但新的虚拟 DOM 不存在,为 unmount 操作
if (container._vnode) {
container.innerHTML = "";
}
}
// 将每次的 vnode 存储到 container 对象上,以便下次更新时作为旧的 vnode 进行比较
container._vnode = vnode;
}patch 函数的基本实现
在 render 函数中,只要本次传入了新的 vnode,无论是首次渲染还是更新渲染,最终都会交给 patch 函数处理
之所以这样设计,是因为首次渲染本质上也可以看作一次特殊的更新:只不过这次更新中旧的 vnode 不存在。因此可以把 "首次挂载" 和 "后续更新" 统一放到 patch 函数中处理
对于 patch 函数来说,它需要知道三个信息:
- 旧的
vnode:用于和新的vnode进行比较 - 新的
vnode:表示本次希望渲染出来的内容 - 容器
container:用于在首次挂载时,将真实 DOM 插入到正确的位置
实现思路
- 如果旧的
vnode不存在,说明这是首次渲染,直接调用mountElement,把新的vnode挂载到容器中 - 如果旧的
vnode存在,说明这是一次更新,此时需要比较新旧vnode,并根据差异更新真实 DOM
function patch(oldVNode, newVNode, container) {
if (!oldVNode) {
mountElement(newVNode, container);
} else {
// 更新逻辑
}
}createRenderer 函数的设计
前面已经实现了 render 和 patch 的基本逻辑,但随着功能继续增加,会发现这些函数之间并不是完全独立的
例如:
render中需要调用patch和unmountpatch中需要调用mountElement和unmountmountElement中在处理子节点时,又需要继续调用patch
也就是说,这些函数共同组成了一套完整的渲染流程,它们之间存在明确的协作关系
如果继续把这些函数散落在外部作用域中,不仅结构会变得松散,后续也不方便继续扩展。因此可以通过一个工厂函数 createRenderer,把和渲染相关的逻辑统一组织到一起
createRenderer
function createRenderer() {
function render(vnode, container) {}
function patch() {}
function unmount() {}
function mountElement() {}
return {
patch,
render,
unmount,
mountElement,
};
}