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

本文深入探讨了Vue.js模板解析与DOM属性设置的问题,解释了在Vue中如何处理HTML属性与DOMProperties的差异,尤其是布尔属性的处理。文章通过代码示例展示了在渲染过程中如何确保属性设置正确,同时提到了只读属性的特殊情况,以及如何优化属性设置的逻辑,以实现与平台无关的属性处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

<button disabled>Buttons</button>

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

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

const button = {
	type: 'button',
	props: {
		disabled: ''
	}
}

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

el.setAttribute('disabled','')

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

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

对应的vnode

const button = {
	type: 'button',
	props: {
		disabled: false
	}
}

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

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

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

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

<button disabled>Buttons</button>

其对应的vnode是:

const button = {
	type: 'button',
	props: {
		disabled: ''
	}
}

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

el.disabled = ''

但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)
}

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

<form id="form1"></form>
<input form="form1" />

上面代码中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)
}

实际上不仅仅是<input/>标签,所有表单元素都具有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)
		}
	}
})

而在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)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值