• 【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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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

    <!-- 没有子节点 -->
    <div></div>
    <!-- 文本子节点 -->
    <div>Some Text</div>
    <!-- 多个子节点 -->
    <div>
    	<p>
    	</p>
    </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    • 没有子节点,此时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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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, '')
    		}
    		// 如果也没有就子节点,那么什么都不要做
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
  • 相关阅读:
    JAVA进程CPU负载过高导致问题分析
    C++ 类成员指针
    数据库系统>并发控制
    C#界面里Form.HelpButton 属性的使用
    如何创建集成 LSP 支持多语言的 Web 代码编辑器
    分享66个Python管理系统源代码总有一个是你想要的
    Python散点图
    【Kotlin基础系列】第7章 类与对象(1)
    第一次线上 OOM 事故,竟和 where 1 = 1 有关
    falco 【1】简介
  • 原文地址:https://blog.csdn.net/loyd3/article/details/126028691