【vue设计与实现】挂载和更新 7-更新子节点

如何更新元素的子节点
首先回顾下元素的子节点是怎么被挂载的,看mountElement函数:

function mountElement(vnode, container){
	const el = vnode.el = createElement(vnode,type)
	// 挂载子节点,首先判断children的类型 字符串,数组等
	// 如果是字符串类型,说明是文本子节点
	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)
}

注意这里在挂载的时候要区分子节点类型,为什么要区分子节点的类型?其实这是一个规范性问题,因为只有子节点的类型是规范化的,才有利于编写更新逻辑。那么应该怎么规范化vnode.children,在搞清楚这个问题之前,先要搞清楚一个HTML页面中,元素的子节点都有哪些情况。如下面HTML代码所示:

<!-- 没有子节点 -->
<div></div>
<!-- 文本子节点 -->
<div>Some Text</div>
<!-- 多个子节点 -->
<div>
	<p>
	</p>
</div>

一个元素的子节点无非以下三种情况

  • 没有子节点,此时vnode.children的值为null
  • 文本子节点,此时vnode.children的值为字符串
  • 其他情况,无论是单个元素子节点,还是多个子节点,都可以用数组来表示

因此渲染器执行更新时,新旧子节点都分别是三种情况之一。因此可以总结出更新子节时有九种可能。(新节点有三种情况,旧节点也有三种情况,组合起来用九种情况)

但落实到代码,会发现其实不需要完全覆盖九种情况,下面先看patchElement函数的代码:

function patchElement(n1,n2){
	const el = n2.el = n1.el
	const oldProps = n1.props
	const newProps = n2.props
	// 第一步 更新props
	for(const key in newProps){
		if(newProps[key] !== oldProps[key]){
			patchProps(el,key,oldProps[key],newProp[key])
		}
		for(const key in oldProps){
			if(!(key in newProps)){
				patchProps(el,key,oldProps[key],null)
			}
		}

		// 第二步 更新children
		patchChildren(n1,n2,el)
	}
}

patchChildren的函数实现如下

function patchChildren(n1,n2,container){
	// 判断新子节点的类型是否是文本节点
	if(typeof n2.children === 'string'){
		// 旧子节点的类型有三种可能
		// 只有当旧子节点为一组子节点时,才需要逐个卸载,其他情况下都不需要做
		if(Array.isArray(n1,children)){
			n1.children.forEach((c)=>{unmount(c1))
		}
		// 最后将新的文本节点内容设置给容器元素
		setElementText(container, n2.childrien )
	}else if(Array.isArray(n2.children)){
		// 如果是一组子节点

		// 先判旧子节点是否也是一组子节点
		if(Array.isArray(n1.children)){
			// 如果是一组子节点,需要用到Diff算法
			// 后面会详细介绍Diff算法
		}else{
			// 这里,旧子节点要么是文本子节点,要么不存在
			// 但无论是哪种情况,都只需要将容器清空,然后将新的一组子节点逐个挂载
			setElement(container, '')
			n2.children.forEach(c => patch(null,c,container))
			
		}
	}else{
		// 代码运行到这里,说明新子节点不存在
		// 如果旧子节点是一组,只需逐个卸载即可
		if(Array.isArray(n1.children)){
			n1.children.forEach(c=>unmount(c))
		}else if(typeof n1.children === 'string'){
			// 旧子节点是文本子节点,清空内容即可
			setElementText(container, '')
		}
		// 如果也没有就子节点,那么什么都不要做
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值