• 【vue设计与实现】挂载和更新 2-正确的设置元素属性


    对于普通的HTML文件来说,浏览器解析HTML代码后,会自动分析HTML Attribute并设置合适的DOM Properties。但是用户编写在 Vue.js的单文件组件中的模板不会被浏览器解析,也就是说,本来要浏览器完成的工作,现在需要框架来完成。
    以禁用按钮为例,如下:

    <button disabled>Buttons</button>
    
    • 1

    这样按钮是禁用的,并且el.disabled也设置为true

    如果同样的代码出现在Vue.js的模板中,情况则不同,首先会编译成如下vnode,等价于:

    const button = {
    	type: 'button',
    	props: {
    		disabled: ''
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果在渲染器中调用setAttribute函数设置属性,相当于:

    el.setAttribute('disabled','')
    
    • 1

    将disabled的属性设置为空字符串,这样做没问题能实现效果,但是考虑如下模板:

    <button :disabled="false">Buttons</button>
    
    • 1

    对应的vnode

    const button = {
    	type: 'button',
    	props: {
    		disabled: false
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    用户的本意是不禁用,但是用过setAttribute设置后,按钮还是被禁用了,
    因为使用setAttribute设置的值总是会被字符串化,所以就相当于

    el.setAttribute('disabled','false')
    
    • 1

    而el.disabled属性值是布尔类型的,且并不关心具体的HTML Attribute的值是什么,只要disabled属性存在,按钮就会被禁用。

    那如果优先设置DOM Properties呢,再看这个例子:

    <button disabled>Buttons</button>
    
    • 1

    其对应的vnode是:

    const button = {
    	type: 'button',
    	props: {
    		disabled: ''
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里props.disabled的值是一个空字符串,用空字符串来设置DOM Properties,相当于

    el.disabled = ''
    
    • 1

    但el.disabled是布尔类型,浏览器会把其空字符串矫正为false,这就违背了用户的本意,希望的是禁用按钮,但是el.disabled = false是不禁用按钮。

    那么就需要特殊处理,即优先设置元素的DOM Properties,当其为空字符串时,手动将值矫正为true,实现如下

    function mountElement(vnode,container){
    	const el = createElement(vnode,type)
    	// 省略children的处理
    	if(vnode.props){
    		for(const key in vnode.props){
    			// 用in操作符判断key是否存在对应的DOM Properties
    			if(key in el){
    				const type = typeof el[key]
    				const value = vnode.props[key]
    				// 如果是布尔类型,并且value是空字符串,则将值矫正为true
    				if(type === 'boolean' && value === ''){
    					el[key] = true
    				}else{
    					el[key] = value
    				}
    			}else{
    				// 如果要设置的属性没有对应的DOM Properties,则使用setAttribute函数设置属性
    				el.setAttribute(key, vnode.pops[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
    • 22
    • 23

    但是上面的实现仍然存在问题,比如有些DOM Properties是只读的,如下面代码:

    <form id="form1"></form>
    <input form="form1" />
    
    • 1
    • 2

    上面代码中el.form是只读的,因此只能通过setAttribute来设置,因此要修改上面的逻辑

    function shouldSetAsProps(el,key,value){
    	// 特殊情况,特殊处理
    	if(key === 'form' && el.tagName === 'INPUT') return false
    	// 兜底
    	return key in el
    }
    
    function mountElement(vnode,container){
    	const el = createElement(vnode,type)
    	// 省略children的处理
    	if(vnode.props){
    		for(const key in vnode.props){
    			
    			const value = vnode.props[key]
    			// 使用shouldSetAsProps来判断是否应该作为DOM Properties设置
    			if(shouldSetAsProps(el,key,value)){
    				const type = typeof el[key]
    				if(type === 'boolean' && value === ''){
    					el[key] = true
    				}else{
    					el[key] = value
    				}
    			}else{
    				el.setAttribute(key, value)
    			}
    			
    		}
    	}
    	insert(el,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

    实际上不仅仅是标签,所有表单元素都具有form属性,都应该作为HTML Attributes设置

    当然还有其他类似这种需要特殊处理的情况,在此就不一一列出来,只要掌握处理问题的思路即可。

    最后还要把属性的设置变成与平台无关,如下面代码所示:

    const renderer = createRenderer({
    	// 用于创建元素
    	createElement(tag){
    		return document.createElement(tag)
    	},
    	// 用于设置元素的文本节点
    	setElementText(el,text){
    		el.textContent = text
    	},
    	// 用于在给定的parent下添加指定元素
    	insert(el,parent,anchor = null){
    		parent.insertBefore(el,anchor)
    	}// 将属性设置相关操作封装到patchProps函数中,并作为渲染器选项传递
    	patchPros(el,key,preValue,nextValue){
    		if(shouldSetAsProps(el,key,nextValue)){
    			const type = typeof el[key]
    			if(type === 'boolean' && nextValue=== ''){
    				el[key] = true
    			}else{
    				el[key] = nextValue
    			}
    		}else{
    			el.setAttribute(key, nextValue)
    		}
    	}
    })
    
    • 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

    而在mountElement函数中,只需要调用patchProps函数,并传递相应参数即可

    
    function mountElement(vnode,container){
    	const el = createElement(vnode,type)
    	// 省略children的处理
    	if(vnode.props){
    		for(const key in vnode.props){
    			//调用patchProps函数即可
    			patchProps(el,key,null,vnode.props[key])
    			
    		}
    	}
    	insert(el,container)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    nero platinum刻录光盘简要教程(文章末尾有教程链接)
    深度学习目标检测模型综述
    MySQL
    高防CDN安全防护系统在业务方面的应用
    Qt设置进制
    卡尔曼滤波:过滤随机游走
    封装axios的两种方式
    HTML5+CSSDAY4综合案例一--热词
    vue实现换一批业务【WoodenFish完整版】
    2731.移动机器人
  • 原文地址:https://blog.csdn.net/loyd3/article/details/125796472