在之前我们就提到,首次渲染之后,后续如果再调用 render 函数时,传递的 vnode 为 null 则表示是卸载。
当时我们是直接通过执行 container.innerHTML = '' 来实现的,但是这样做会有以下几个问题,如下:
- 容器内可能是由某个或者多个组件渲染的,当卸载操作发生时,应该正确的调用这些组件的 beforUnmount、unmounted 等生命周期函数。
- 即使内容不是由组件渲染的,有的元素存在自定义指令,我们应该再在卸载操作发生时正确的执行这些对应指令的钩子函数。
- 使用 innerHTML 清空容器的还有一个缺陷是,它并不会移除绑定在 DOM 元素上的事件处理函数
正确的卸载方式应该是,根据 vnode 对象获取对应与其相关联的真实 DOM 元素,然后使用原生 DOM 操作方式将该元素移除。为此,我们需要再 vnode 与真实 DOM 元素之间建立联系,修改 mountElement 函数,如下:
javascript
function mountElement(vnode, container) {
// 让 vnode.el 引用真实的 dom 元素
const el = (vnode.el = hostCreateElement(vnode.type))
// ... 省略其他代码
}
这样建立联系之后,当卸载的时候,只需要根据 vnode.el 属性即可获取真实的 dom 元素,然后在将其从父元素中移除,如下:
javascript
function render(vnode, container) {
if (vnode) {
patch(container._vnode, vnode, container)
} else {
// 卸载操作
if (container._vnode) {
// 获取 vnode 关联的真实 dom
const { el } = container._vnode
// 获取 el 的父元素
const parent = el.parentNode
// 调用父元素的 removeChild 方法
if (parent) {
parent.removeChild(el)
}
}
container._vnode = vnode
}
}
根据之前的设计方案,这个卸载子元素的操作,会经常用到,我们将其提取出来,封装到 unmount 函数中,如下:
javascript
function unmount(vnode) {
const { el } = vnode
const parent = el.parentNode
if (parent) {
parent.removeChild(el)
}
}