8.1 挂载子节点和元素的属性
元素可以有多个子节点,也可以带一堆属性;渲染器要能把
children与props分别落到真实 DOM 上。
一、用数组描述子节点
一个元素除了文本子节点,还可以包含其他元素子节点,并且子节点可以是很多个。这类情况统一用数组描述:
const vnode = {
type: 'div',
children: [
{ type: 'p', children: 'hello' }
]
}
挂载时遍历数组,逐个调用 patch:
function mountElement(vnode, container) {
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)
})
}
insert(el, container)
}
两个参数别写错:
patch的第一个参数是null(挂载阶段没有旧 vnode,直接传null即可),第三个参数是挂载点(刚刚创建的el),这样才能保证子节点挂到正确位置。
二、用 props 描述元素属性
vnode.props 是一个对象:键是属性名称,值是属性值。遍历它就能把属性设置到对应元素上:
const vnode = {
type: 'div',
props: { // 新增
id: 'foo'
},
children: [
{ type: 'p', children: 'hello' }
]
}
// mountElement 中
if (vnode.props) {
for (const key in vnode.props) {
// 调用 setAttribute 将属性设置到元素上
el.setAttribute(key, vnode.props[key])
}
}
三、这版实现为什么还不够
渲染一个元素的属性比想象中要复杂,setAttribute 只是「能用」的第一版:
| 隐患 | 说明 |
|---|---|
| 值被字符串化 | setAttribute 设置的值总是被字符串化,布尔属性会失真 |
| 名字不一致 | class 对应的 DOM 属性其实是 el.className |
| 没有对应属性 | aria-* 这类属性没有 DOM Properties,只能用 setAttribute |
| 只读属性 | 部分 DOM Properties 只读,直接赋值无效,必须走 setAttribute |
于是「到底该用
setAttribute还是直接设置 DOM 对象」成为一个独立问题,见 8.2 与 8.3。
相关
- 8.2 HTML Attributes 与 DOM Properties:两种属性的差异与关联
- 8.3 正确地设置元素属性:
shouldSetAsProps的判断原则与patchProps