在上一节说了解了一下元素属性的挂载,主要内容就是如何正确的挂载元素的属性,HTML Attributes
和DOM Properties
两者之间的区别以及应用,还有在Vue中是如何对class、style属性进行增强的,前面说完了挂载阶段下面再说下卸载阶段。
首先回顾一下在渲染器章节中的代码,在初次挂载完成之后,后续渲染就会触发更新,所以卸载操作时发生在更新阶段的,在卸载阶段也就是给renderer
函数传递null
参数时候,在patch
函数中不能通过使用container.innerHTML=""
方式来清空。
function createRender() {
function render(vnode, container) {
if (vnode) { // 挂载或更新阶段
patch(container._vnode, vnode, container)
} else {
// 旧的vnode存在,新的vnode不存在 卸载阶段
if (container._vnode) {
container.innerHTML = "";
}
}
container._vnode = vnode;
}
return {
render
}
}
const renderer = createRenderer()
// 首次渲染 挂载阶段
renderer(vnode1, document.getElementById("#app"))
// 第二次渲染 更新阶段
renderer(vnode2, document.getElementById("#app"))
// 卸载阶段
renderer(null, document.getElementById("#app"))
这样做有以下几个缺陷:
- 使用innerHTML虽然可以清空元素内容,但是不会清除绑定在DOM元素上的事件处理函数;
- 容器中的元素可能是某个或多个组件渲染的,当需要进行卸载操作时应该正确调用组件对应卸载的生命周期函数;
- 若元素中存在自定义指令在卸载时也应该执行指令对应的钩子函数;
那么正确的做法应该是使用原生DOM操作方法将真实DOM元素移除,但是在卸载阶段并没有办法获取到真实DOM,所以我们可以根据vnode对象获取与其相关联的真实DOM元素,关键的一步就是怎么在vnode对象与真实DOM元素之间建立联系。
function mountElement(vnode,container){
// 在此处建立联系
const el = vnode.el = document.createElement(vnode.type);
// ...
}
function render(vnode, container) {
if (vnode) { // 挂载或更新阶段
patch(container._vnode, vnode, container)
} else {
if (container._vnode) {
const el = container._vnode.el;
const parent = el.parentNode;
if(parent) parent.removeChild(el);
}
}
container._vnode = vnode;
}
可以在挂载阶段将创建的真实DOM元素直接挂载到vnode虚拟节点中进行关联,在挂载阶段建立起联系之后,在卸载的时候就可以从旧vnode中获取到关联的真实DOM,通过调用removeChild
函数将其从父元素中删除。由于卸载操作在后续可能会被复用所以可以将其封装成一个函数。
function unmount(vnode){
const parent = vnode.el.parentNode;
if(parent) parent.removeChild(vnode.el);
}
funciton render(vnode,container){
// ...
if (container._vnode) {
unmount(container._vnode);
}
}
从整体或者说宏观角度来说这就是卸载阶段要做的所有事情。