7.2 渲染器的基本概念
先把术语与骨架定清楚:渲染器 把 虚拟 DOM 挂载 到容器中,更新时通过 打补丁 复用旧节点。
一、核心术语
| 术语 | 英文 | 含义 |
|---|---|---|
| 渲染器 | renderer | 把 虚拟 DOM 渲染为特定平台真实元素的对象;是比 render 更宽泛的概念 |
| 渲染 | render | 动词,指具体的渲染动作;render 函数只是渲染器的一部分 |
| 虚拟 DOM | virtual DOM / vdom | 与真实 DOM 结构一致的树形结构 |
| 虚拟节点 | vnode | 树中的任意一个节点;任意 vnode 都可以是一棵子树,统一用 vnode 表达 |
| 挂载 | mount | 把 vnode 渲染为真实 DOM 节点并放入容器的过程(mounted 钩子中即可访问真实 DOM) |
| 容器 | container | 挂载点,一个 DOM 元素;渲染器把内容渲染到其中 |
| 打补丁 | patch | 用 newVNode 与上一次的 oldVNode 比较,找出并更新变更点 |
挂载本身也可看作一种特殊的打补丁,特殊之处在于旧 vnode 不存在,不必过分纠结两者区别。
二、为什么要有 createRenderer
- 渲染器 ≠ 渲染:渲染器内容更广泛,
render只是其中一部分; - 除了渲染,渲染器还要激活已有 DOM 元素,这发生在同构渲染场景;
- 连创建应用的
createApp函数也是渲染器的一部分。
因此用 createRenderer() 创建渲染器,再调用 renderer.render(vnode, container) 执行渲染。
三、render:首次挂载与后续更新
function createRenderer() {
function render(vnode, container) {
if (vnode) {
// 新 vnode 存在:与旧 vnode 一起交给 patch 打补丁
patch(container._vnode, vnode, container)
} else {
if (container._vnode) {
// 新 vnode 不存在而旧 vnode 存在 → 卸载(unmount):清空容器
container.innerHTML = ''
}
}
// 缓存 vnode 作为「下一次渲染的旧 vnode」
container._vnode = vnode
}
return { render }
}
- 首次调用
render:container._vnode是undefined,只涉及创建新元素(挂载); - 同一 container 再次调用:渲染器不仅要挂载新节点,还要执行更新动作;
container._vnode一份数据承担两个作用:缓存上一次的 vnode(供下次 diff),以及判断是初次挂载还是更新。
四、patch:渲染器的核心入口
// n1:旧 vnode;n2:新 vnode
function patch(n1, n2, container) {
if (!n1) {
// 旧 vnode 不存在 → 挂载:忽略 n1,把 n2 描述的内容渲染到容器中
mountElement(n2, container)
} else {
// 旧 vnode 存在 → 打补丁,比较并更新差异
}
}
patch承载了最重要的渲染逻辑,既能打补丁也能挂载;- 首次渲染时传入的第一个参数
n1是undefined,因此挂载逻辑与更新逻辑共用一个入口; - 具体挂载实现见 7.3 自定义渲染器。
见名知意:新 vnode 统一叫
n2,旧 vnode 统一叫n1。
相关
- 7.1 渲染器与响应系统的结合:用 effect 自动驱动渲染器执行
- 7.3 自定义渲染器:
mountElement的实现,以及如何抽象平台 API 实现跨平台