⚡ 30 秒速记
- 按节点类型分派挂载逻辑
- 元素创建后应用属性,再处理孩子与插入位置
- 文本与片段不走普通元素的全部步骤
- 命名空间要正确继承并处理切换边界
- 组件挂载先得到内部子树
挂载是把一份界面描述第一次变成宿主环境里的实际内容。 普通元素要创建节点、设置属性、挂载孩子并放到目标位置;文本和片段有不同处理方式,组件还得先执行内部渲染。保存宿主引用后,后续更新才能找到对应结果。遇到矢量图等命名空间时,创建方式和后代规则也要正确,不能所有标签都当普通页面元素处理。
版本校准: 本文出现 Object.defineProperty、Dep、Watcher 和双端 Diff 时,主要描述 Vue 2 实现;Vue 3 使用 Proxy、effect 与新的渲染器路径。Vue 2 已于 2023-12-31 结束维护,新项目应以 Vue 3 为基线,旧项目参考 Vue 2 EOL 官方说明 制定迁移与安全策略。
本章主要讲解渲染器将各种类型的 VNode 挂载为真实 DOM 的原理,阅读本章内容你将对 Fragment 和 Portal 有更加深入的理解,同时渲染器对有状态组件和函数式组件的挂载实际上也透露了有状态组件和函数式组件的实现原理,这都会包含在本章的内容之中。另外本章的代码将使用上一章所编写的 h 函数,所以请确保你已经阅读了上一章的内容。
# 责任重大的渲染器
所谓渲染器,简单的说就是将 Virtual DOM 渲染成特定平台下真实 DOM 的工具(就是一个函数,通常叫 render),渲染器的工作流程分为两个阶段:mount 和 patch,如果旧的 VNode 存在,则会使用新的 VNode 与旧的 VNode 进行对比,试图以最小的资源开销完成 DOM 的更新,这个过程就叫 patch,或“打补丁”。如果旧的 VNode 不存在,则直接将新的 VNode 挂载成全新的 DOM,这个过程叫做 mount。
通常渲染器接收两个参数,第一个参数是将要被渲染的 VNode 对象,第二个参数是一个用来承载内容的容器(container),通常也叫挂载点,如下代码所示:
function render(vnode, container) {
const prevVNode = container.vnode
if (prevVNode == null) {
if (vnode) {
// 没有旧的 VNode,只有新的 VNode。使用 `mount` 函数挂载全新的 VNode
mount(vnode, container)
// 将新的 VNode 添加到 container.vnode 属性下,这样下一次渲染时旧的 VNode 就存在了
container.vnode = vnode
}
} else {
if (vnode) {
// 有旧的 VNode,也有新的 VNode。则调用 `patch` 函数打补丁
patch(prevVNode, vnode, container)
// 更新 container.vnode
container.vnode = vnode
} else {
// 有旧的 VNode 但是没有新的 VNode,这说明应该移除 DOM,在浏览器中可以使用 removeChild 函数。
container.removeChild(prevVNode.el)
container.vnode = null
}
}
}