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


    卸载操作

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

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

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

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

    在前面我们实现的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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    从上面的代码可以看到,调用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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    function unmount(vnode){
    	const parent = vnode.el.parentNode
    	if(parent){
    		parent.removeChild(vnode.el)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    function render(vnode,container){
    	if(vnode){
    		patch(container._vnode,vnode,container)
    	}else{
    		if(container._vnode){
    			unmount(container._vnode)
    		}
    	}
    	container._vnode = vnode
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    区分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{
    		// 更新操作
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    但是新旧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
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

  • 相关阅读:
    Jmeter自带函数不够用?不如自己动手开发一个
    Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
    ArcGIS for Android 禁止地图旋转
    代理IP与Socks5代理:跨界电商智能爬虫的引擎与安全壁垒
    cesium 实体无法拾取
    window与linux共享文件夹,samba的配置和使用
    uniapp 可输入可选择的........框
    计算机毕业设计 SSM+Vue社区疫情防控管理系统 小区疫情防疫管理系统 物业管理系统Java Vue MySQL数据库 远程调试 代码讲解
    PostMan、ApiFox等工具Post请求中@RequestParam和@RequestBody的混合使用如何传参
    zigbee笔记:七、zigbee系统电源管理与睡眠唤醒
  • 原文地址:https://blog.csdn.net/loyd3/article/details/125829481