[HTML] HTML Attibutes与DOM Properties
因为有一些属性,在HTML标签上的名字和DOM Properties API中的名字是不一致的,比如最常用的,标签上的class属性,DOM Properties中是className
另外,并不是所有 HTML Attributes 都有与之对应的 DOM Properties,例如:
<div aria-valuenow="75"></div>
aria-* 类的 HTML Attributes 就没有与之对应的 DOM Properties
同样的,反过来,并不是所有的DOM Properties都有与之对应的HTML Attributes,比如我们可以用el.textContent 来设置元素的文本内容,但是HTML Attributes中并没有textContent这样的属性
还有一些情况,会造成一些误解,比如:
<input value="foo" />
我们一般情况下,是通过el.value,来获取input标签上的值,当然,我们其实也可以通过el.getAttribute('value')来获取。但是当用户在页面改了值之后,比如:
<input value="bar" />
通过el.value能够获取修改之后的值bar,但是el.getAttribute('value')获取的还是原来的foo。
这些关系虽然复杂,但其实我们只需要记住一个核心原则即可:HTML Attributes的作用是设置之对应与的DOM Properties 的初始值。
虚拟DOM导致元素设置的问题
我们现在有这样的代码:
<button disabled>Button</button>
根据我们上面得出结论,HTML Attributes的作用是设置之对应与的DOM Properties 的初始值。
浏览器在解析这段 HTML 代码时,发现这个按钮存在一个叫作disabled 的 HTML Attributes,于是浏览器会将该按钮设置为禁用状态,并将它的 el.disabled 这个 DOM Properties 的值设置为true。
但是这个情况在虚拟DOM中会出现一些问题,因为如果我们直接处理话,我们会写成下面的样子:
const vnode = {
type: 'button',
props: {
disabled: ''
},
children: 'hello world'
}
这当然没有什么问题,这段虚拟DOM现在在我们的代码中能正确的执行。因为在我们的代码中执行,相当于:
el.setAttribute('disabled', '')
但是,大多数情况下,我们对于这种布尔值的情况,会带上true或者false,比如,我现在写成disabled: 'false',就表示不要禁用了。
const vnode = {
type: 'button',
props: {
disabled: 'false'
},
children: 'hello world'
}
你会发现,el.setAttribute("disabled",false), el.setAttribute("disabled",true)或者el.setAttribute("disabled",'')结果是不变的,都相当于true的效果,但是使用el.disabled=true,el.disabled=false,或者el.disabled='',结果又是不一样的,这很容易给开发者造成心智负担,所以这里我们简单做一下处理
function mountElement(vnode, container) {
// 创建 DOM 元素
const el = createElement(vnode.type);
if (typeof vnode.children === "string") {
// 如果子节点是字符串,说明它是文本节点
setElementText(el, vnode.children);
}
else if(Array.isArray(vnode.children)){
// 如果子节点是数组,说明它是多个子节点,遍历子节点,并且通过patch挂载它们
vnode.children.forEach(child => {
patch(null,child, el);
});
}
// 如果存在props属性,遍历props属性,并且将其设置到el上
if(vnode.props){
for(const key in vnode.props){
// 使用in操作符判断key是否存在在对应的DOM Properties上
if(key in el){
// 判断该DOM Properties的类型
const type = typeof el[key];
const value = vnode.props[key];
// 如果是布尔类型,并且value是空字符串'',那么就是设置为true
if(type === 'boolean' && value === ''){
el[key] = true;
}
else{
//其他情况按照DOM操作本身的规则处理
el[key] = value;
}
}
else{
// 如果key不存在在DOM Properties上,那么就是setAttribute
el.setAttribute(key, vnode.props[key]);
}
}
}
insert(el, container);
}
当然,现在还是有一些问题,因为有一些DOM Properties是只读的,我们并不能设置,比如
<form id="form1"></form>
<input form="form1" />
DOM Properties 的 el.form并不能直接设置,是只读的,如果要设置只能通过setAttribute函数来处理,所以,我们这里可以做一下特殊处理。
function shouldSetAsProps(el, key, value) {
// 特殊处理
if (key === 'form' && el.tagName === 'INPUT') return false
// 判断属性是否在el上,返回true表示是el上的属性
return key in el
}
这里我们需要再修改一下mountElement函数,如果属性不能作为 DOM Properties 被设置,应该使用 setAttribute 函数来设置
function mountElement(vnode, container) {
// 创建 DOM 元素
const el = createElement(vnode.type);
if (typeof vnode.children === "string") {
// 如果子节点是字符串,说明它是文本节点
setElementText(el, vnode.children);
}
else if(Array.isArray(vnode.children)){
// 如果子节点是数组,说明它是多个子节点,遍历子节点,并且通过patch挂载它们
vnode.children.forEach(child => {
patch(null,child, el);
});
}
// 如果存在props属性,遍历props属性,并且将其设置到el上
if(vnode.props){
for(const key in vnode.props){
const value = vnode.props[key];
// 使用 shouldSetAsProps 函数判断是否应该作为 DOM Properties设置
if(shouldSetAsProps(el, key, value)){
// 判断该DOM Properties的类型
const type = typeof el[key];
// 如果是布尔类型,并且value是空字符串'',那么就是设置为true
if(type === 'boolean' && value === ''){
el[key] = true;
}
else{
el[key] = value;
}
}
else{
// 如果key不存在在DOM Properties上,那么就是setAttribute
el.setAttribute(key, vnode.props[key]);
}
}
}
insert(el, container);
}
当然,这里的处理,我们最好也放入到平台无关性的配置中,因此我们可以将属性的设置,提取为一个函数patchProps
function shouldSetAsProps(el, key, value) {
// 特殊处理
if (key === 'form' && el.tagName === 'INPUT') return false
// 兜底
return key in el
}
const options = {
createElement(tag) {
return document.createElement(tag);
},
// 用于设置元素的文本节点
setElementText(el, text) {
el.textContent = text;
},
// 用于在给定的 parent 下添加指定元素
insert(el, parent, anchor = null) {
parent.insertBefore(el, anchor);
},
patchProps(el, key, prevValue, nextValue) {
if(shouldSetAsProps(el, key, nextValue)){
// 判断该DOM Properties的类型
const type = typeof el[key];
// 如果是布尔类型,并且value是空字符串'',那么就是设置为true
if(type === 'boolean' && nextValue === ''){
el[key] = true;
}
else{
el[key] = nextValue;
}
}
else{
// 如果key不存在在DOM Properties上,那么就是setAttribute
el.setAttribute(key, nextValue);
}
}
};

浙公网安备 33010602011771号