子节点挂载
当 vnode 的 children 是数组时,需要递归挂载每个子节点:
function mountElement(vnode, container) {
const 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) // 递归挂载子节点
})
}
// 处理 props
if (vnode.props) {
for (const key in vnode.props) {
patchProps(el, key, null, vnode.props[key])
}
}
insert(el, container)
}
javascript
DOM Properties vs HTML Attributes
这是渲染器属性处理中的核心概念区分。
两者的区别
| 概念 | 说明 | 示例 |
|---|---|---|
| HTML Attributes | 定义在 HTML 标签上的属性 | <input type="text" disabled> |
| DOM Properties | DOM 节点对象上的属性 | input.disabled = true |
名称不一致的属性
| HTML Attribute | DOM Property |
|---|---|
class | className |
for | htmlFor |
tabindex | tabIndex |
readonly | readOnly |
text content | textContent |
一对多关系
<input> 的 value attribute 可能对应两个 DOM properties:
el.value:当前输入值(可变)el.defaultValue:初始值(HTML attribute 值)
disabled 属性的特殊处理
// 问题:空字符串和 false 都会导致 disabled 生效
el.setAttribute('disabled', '') // disabled ✅
el.setAttribute('disabled', false) // disabled ✅(false 被转为字符串 "false")
// 正确做法:直接操作 DOM property
el.disabled = true // 禁用
el.disabled = false // 启用
el.disabled = '' // 禁用(空字符串被转为 true)
javascript
patchProps 实现
function patchProps(el, key, prevValue, nextValue) {
// 判断 key 是否存在于 DOM properties 中
if (key in el) {
const type = typeof el[key]
// 特殊情况:form 和 input 的 form 属性不可修改
if (key === 'form' && el.tagName === 'INPUT') return
if (typeof nextValue === 'boolean' && nextValue === false) {
el[key] = false
} else if (nextValue === '') {
// 空字符串在 boolean 上下文中应转为 true
el[key] = true
} else {
el[key] = nextValue
}
} else {
// 不存在于 DOM properties,使用 setAttribute
if (nextValue === null || nextValue === undefined) {
el.removeAttribute(key)
} else {
el.setAttribute(key, nextValue)
}
}
}
javascript
特殊属性检查函数
function shouldSetAsProps(el, key, value) {
// form 属性在 input 标签上是只读的
if (key === 'form' && el.tagName === 'INPUT') return false
// 其他情况使用 DOM property
return key in el
}
javascript
跨平台考虑
将 patchProps 逻辑提取到 createRenderer 的 options 中,使属性处理逻辑也能在非浏览器环境中使用:
const renderer = createRenderer({
createElement(tag) { /* ... */ },
setElementText(el, text) { /* ... */ },
insert(el, parent, anchor) { /* ... */ },
patchProps(el, key, prevValue, nextValue) {
// 属性处理逻辑
}
})
javascript
这样无论是浏览器环境还是 Node.js(SSR)环境,属性处理逻辑都是可复用的。
↑