【vue设计与实现】挂载和更新 4-卸载操作&区分vnode的类型

卸载操作

卸载操作发生在更新阶段,更新指的是,在初次挂在完成之后,后续渲染会触发更新,如下面代码所示:

// 初次挂载
renderer.render(vnode,document.querySelector('#app'))
// 再次挂载触发更新
renderer.render(newVnode,document.querySelector('#app'))

更新的情况有好几种,
首先,当后续调用render函数渲染控内容(即null)时,如下面代码所示:

// 初次挂载
renderer.render(vnode,document.querySelector('#app'))
// 新vnode为null,意味着卸载之前渲染的内容
renderer.render(null,document.querySelector('#app'))

在前面我们实现的render函数中,是直接通过innerHTML清空容器,但这样是不严谨的,原因有三点:

  1. 容器的内容可能是某个或多个组件渲染的,当卸载操作发生时,应该要正确地调用这些组件的beforeUnmountunmounted等生命周期函数
  2. 就算内容不是由组件渲染的,有的元素存在自定义指令,应该在卸载操作发生时正确执行对应的指令钩子函数
  3. 使用innerHTML不会移除绑定在DOM元素上的事件处理函数

正确地卸载方式是:根据vnode对象获取与其相关联的真实DOM元素,然后使用原生DOM操作方法将该DOM元素移除
所以需要在vnode和真实DOM元素之间建立联系,也就是要修改mountElement函数,代码如下:

function mountElement(vnode, container){
	// 让vnode.el引用真实DOM元素
	const el = vnode.el = createElement(vnode,type)
	if(typeof vnode.children === 'string'){
		setElementText(el, vnode.children)
	}else if(Array.isArray(vnode.children)){
		vnode.children.forEach(child=>{
			patch(null,child,el)
		})
	}
	if(vnode.props){
		for(const key in vnode.props){
			patchProps(el,key,null,vnode.props[key])
		}
	}
	insert(el,container)
}

从上面的代码可以看到,调用createElement函数创建真实DOM元素时,会把真实DOM元素赋值给vnode.el属性,这样就在vnode和真实DOM元素之间建立了联系,因此有了这些只需要根据虚拟节点对象vnode.el取得真实DOM元素,再将其从父元素中移除即可,看下面代码:

function render(vnode,container){
	if(vnode){
		patch(container._vnode,vnode,container)
	}else{
		if(container._vnode){
			// 根据vnode获取要卸载的真实DOM元素
			const el = container._vnode.el
			// 获取el的父元素
			const parent = el.parentNode
			// 调用removeChild
			if(parent) parent.removeChild(el)
		}
	}
	container._vnode = vnode
}

由于卸载操作比较常见且基础,所以将其封装到unmount函数中,后续可以复用,代码如下:

function unmount(vnode){
	const parent = vnode.el.parentNode
	if(parent){
		parent.removeChild(vnode.el)
	}
}

有了unmount函数后,就可以在render函数中完成卸载工作了

function render(vnode,container){
	if(vnode){
		patch(container._vnode,vnode,container)
	}else{
		if(container._vnode){
			unmount(container._vnode)
		}
	}
	container._vnode = vnode
}

区分vnode的类型

当后续调用render函数渲染空内容(即null)时,会执行卸载操作。如果后续渲染时,为render函数传递了新的vnode,则不会进行卸载操作,而是把新旧vnode都传递给patch函数进行打补丁操作,但是在执行打补丁操作之前,需要保证新旧vnode所描述的内容相同,就比如说,初次渲染的vnode是一个p元素,后续又渲染了一个input元素,这就会造成新旧vnode描述的内容不同,即vnode.type属性的值不同。这样打补丁是没有意义的,因为对于不同的元素来说,每个元素都有特有的属性。
在这种情况下,要先将p元素卸载,再将input元素挂载到容器中。因此需要调整patch函数的代码:

function patch(n1,n2,container){
	// 如果n1存在,则对比n1和n2的类型
	if(n1 && n1.type !== n2.type){
		// 如果新旧vnode的类型不同,则直接将旧的vnode卸载
		unmount(n1)
		// 卸载完后,要将n1的值重置为null,保证后续挂载操作正确执行
		n1 = null
	}
	if(!n1){
		mountElement(n2,container)
	}else{
		// 更新操作
	}
}

但是新旧vnode仅仅是描述的内容相同是不够的,还要确认类型是否相同,要根据不同的类型,提供不同的挂载或打补丁的处理方式,所以要继续完善patch函数,代码如下:

function patch(n1,n2,container){
	// 如果n1存在,则对比n1和n2的类型
	if(n1 && n1.type !== n2.type){
		// 如果新旧vnode的类型不同,则直接将旧的vnode卸载
		unmount(n1)
		// 卸载完后,要将n1的值重置为null,保证后续挂载操作正确执行
		n1 = null
	}
	const {type} = n2
	// 如果n2.type的类型是字符串,则描述的是普通标签元素
	if(typeof type === 'string'){
		if(!n1){
			mountElement(n2,container)
		}else{
			// 更新操作
		}
	}else if(typeof type === 'object'){
		// 如果类型是对象则描述的是组件
	}else if(typeof type === 'xxx'){
		// 其他类型的vnode
	}
	
}

根据vnode.type类型的不同,做相应的处理

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值