8.3 正确地设置元素属性
普通 HTML 文件里,浏览器解析标签后会自动分析 HTML Attributes 并设置合适的 DOM Properties;但 Vue.js 单文件组件中的模板不会被浏览器解析,这份工作必须由框架完成。
一、setAttribute 的缺陷:值总被字符串化
<button :disabled="false">Button</button>
期望按钮不禁用,实际上:
el.setAttribute('disabled', false)
// 等价于
el.setAttribute('disabled', 'false')
// disabled 是布尔属性,只要属性存在按钮就被禁用 → 与期望相反
反过来,直接设置 DOM Properties 也有坑:el.disabled 是布尔类型,把它设为空字符串时,浏览器会把值矫正成布尔 false,于是 <button disabled> 又失效。
所以:优先设置 DOM Properties,但当值为空字符串且该 Property 是布尔类型时,要手动把值矫正为
true。
二、只读 DOM Properties:只能 setAttribute
el.form 是只读的,直接赋值不生效,必须用 setAttribute('form', 'my-form')。这类属性(所有表单元素都具有 form 属性)应该作为 HTML Attributes 被设置,于是抽出一个判断函数:
function shouldSetAsProps(el, key, value) {
// 特殊处理:只读属性只能用 setAttribute
if (key === 'form' && el.tagName === 'INPUT') return false
// 兜底:用 in 操作符判断 key 是否存在对应的 DOM Properties
return key in el
}
| 情况 | 做法 |
|---|---|
key in el 成立 | 作为 DOM Properties 设置,并处理布尔值矫正 |
| 不存在对应 Property | 用 setAttribute 设置 |
只读 Property(如 el.form) | 直接返回 false,走 setAttribute |
不可能把所有需要特殊处理的地方都记住,更何况有时根本不知道什么情况下才需要特殊处理;用
in兜底 + 后续「见招拆招」补特例,代码才会越来越健壮。
三、抽成 patchProps,做成平台无关
属性的设置方式与平台相关(浏览器 / 小程序 / Canvas 各不相同),因此把属性设置从渲染器核心中抽离,放进渲染器选项:
const renderer = createRenderer({
createElement(tag) { return document.createElement(tag) },
setElementText(el, text) { el.textContent = text },
insert(el, parent, anchor = null) { parent.insertBefore(el, anchor) },
// 将属性设置相关操作封装到 patchProps 函数中,并作为渲染器选项传递
patchProps(el, key, prevValue, nextValue) {
if (shouldSetAsProps(el, key, nextValue)) {
// 获取该 DOM Properties 的类型
const type = typeof el[key]
// 布尔类型且值为空字符串 → 矫正为 true
if (type === 'boolean' && nextValue === '') {
el[key] = true
} else {
el[key] = nextValue
}
} else {
// 没有对应的 DOM Properties,则使用 setAttribute 设置
el.setAttribute(key, nextValue)
}
function shouldSetAsProps(el, key, value) {
if (key === 'form' && el.tagName === 'INPUT') return false
return key in el
}
}
})
挂载时统一调用它,prevValue 传 null 表示挂载阶段:
if (vnode.props) {
for (const key in vnode.props) {
patchProps(el, key, null, vnode.props[key])
}
}
patchProps(el, key, prevValue, nextValue)的四个参数是为更新准备的:更新时要把旧值一并传入,才能比较、才能卸载(见 8.9)。
相关
- 8.2 HTML Attributes 与 DOM Properties:为什么不能只用一种方式设置属性
- 8.4 class的处理:
patchProps中的第一个特例分支