卸载的基本实现
约 737 字大约 2 分钟
2026-05-15
卸载的基本实现
建立
vnode和真实 DOM 之间的联系在
render的实现中,若新vnode不存在,但旧vnode存在时。是通过container.innerHTML为空来清空容器的,这种做法虽然简单,但它只是把结果 "看起来删掉了",并没有真正解决卸载过程中的边界问题真正的问题是
- 组件的卸载生命周期不会执行
- 自定义指令的卸载逻辑不会执行
- 已绑定的事件、引用关系也都没有被正确清理
重要
卸载的目标不是清空容器 ,而是根据
vnode找到它对应的真实 DOM,再把这个 DOM 从父节点中移除实现思路
建立
vnode和真实 DOM 之间的联系function mountElement(vnode, container) { const el = vnode.el = createElement(vnode.type); // .. }这里在创建真实 DOM 元素时,会把真实 DOM 元素赋值给
vnode.el属性,这样也就建立了虚拟节点和真实 DOM 之间的联系。一旦vnode.el建立起来,后续无论是更新还是卸载,都可以通过虚拟节点直接拿到真实 DOMfunction unmount(vnode) { const parent = vnode.el.parentNode; if (parent) { parent.removeChild(vnode.el); } } function render(vnode, container) { if (vnode) { patch(container._vnode, vnode, container) } else { if (container._vnode) { unmount(container._vnode); } } container._vnode = vnode; }这里先简单处理了卸载的逻辑,后续在组件和指令章节会补充更多细节
不同类型节点的更新处理
当新旧
vnode的type不同时,说明它们之间没有任何可比性了,这时候就不应该再走更新的流程了,而是应该直接卸载旧节点,再挂载新节点例如第一次渲染的是
p,第二次渲染的是input:const vnode1 = { type: "p", }; const vnode2 = { type: "input", };这两种节点之间其实并不存在 "打补丁" 的意义。因为它们本身就是两种完全不同的元素类型,对应的属性、行为、子节点处理方式都可能不同
实现思路
- 先卸载旧节点
- 挂载新节点
function patch(oldVNode, newVNode, container) { // 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载 if (oldVNode && oldVNode.type !== newVNode.type) { unmount(oldVNode); oldVNode = null; // 卸载后将 oldVNode 置空,以便后续逻辑按首次挂载处理 } if (!oldVNode) { mountElement(newVNode, container); } else { // 更新逻辑 } }到目前为止,默认
vnode.type是字符串,也就是普通元素标签,例如div、p、input。但在 Vue 的完整渲染体系中,type还可能表示组件、Fragment或其他内置类型因此,
patch最终不能只处理字符串类型,而是需要先根据type做分发处理function patch(oldVNode, newVNode, container) { if (oldVNode && oldVNode.type !== newVNode.type) { unmount(oldVNode); oldVNode = null; } const { type } = newVNode; if (typeof type === "string") { if (!oldVNode) { mountElement(newVNode, container); } else { // 元素更新 console.log("打补丁"); } } else if (typeof type === "object") { // 组件处理 console.log("组件处理"); } else { // 其他类型 console.log("未知类型"); } }
