主题
实现Fragment
本节课代码地址(请参考课程获取) 为了提高组件结构灵活性,需要实现
Fragment,具体来说,需要区分几种情况:
1. Fragment包裹其他组件
jsx
<>
<div></div>
<div></div>
</>
// 对应DOM
<div></div>
<div></div>这种情况的JSX转换结果
jsxs(Fragment, {
children: [
jsx("div", {}),
jsx("div", {})
]
});type 为Fragment 的ReactElement ,对单一节点的Diff 需要考虑Fragment 的情况。
2. Fragment与其他组件同级
jsx
<ul>
<>
<li>1</li>
<li>2</li>
</>
<li>3</li>
<li>4</li>
</ul>
// 对应DOM
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>这种情况的JSX转换结果
jsxs('ul', {
children: [
jsxs(Fragment, {
children: [
jsx('li', {
children: '1'
}),
jsx('li', {
children: '2'
})
]
}),
jsx('li', {
children: '3'
}),
jsx('li', {
children: '4'
})
]
});children 为数组类型,则进入reconcileChildrenArray 方法,数组中的某一项为 Fragment ,所以需要增加「type为Fragment的ReactElement的判断」,同时 beginWork 中需要增加Fragment 类型的判断。
3. 数组形式的Fragment
jsx
// arr = [<li>c</li>, <li>d</li>]
<ul>
<li>a</li>
<li>b</li>
{arr}
</ul>
// 对应DOM
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
<li>d</li>
</ul>这种情况的JSX转换结果
jsxs('ul', {
children: [
jsx('li', {
children: 'a'
}),
jsx('li', {
children: 'b'
}),
arr
]
});children 为数组类型,则进入reconcileChildrenArray 方法,数组中的某一项为数组, 所以需要增加「reconcileChildrenArray中数组类型的判断」。
Fragment对ChildDeletion的影响
ChildDeletion 删除DOM 的逻辑:找到子树的根Host节点 找到子树对应的父级Host节点 从父级Host节点中删除子树根Host节点 考虑删除p节点的情况:
jsx
<div>
<p>xxx</p>
</div>考虑删除Fragment 后,子树的根Host 节点可能存在多个:
jsx
<div>
<>
<p>xxx</p>
<p>yyy</p>
</>
</div>对React的影响
React 包需要导出Fragment ,用于JSX 转换引入Fragment 类型 详见fix: react 导出Fragment 纠错 当嵌套数组类型JSX (比如这个Demo)时,由于我们实现的源码中updateFromMap 方法中 如下代码没有考虑传入的element 可能为数组形式:
const keyToUse = element.key !== null ? element.key : index;导致element 为数组形式时keyToUse 为undefined ,进而导致Fragment 不能复用,造 成bug 。 为了解决这个问题,这种情况下可以使用index 作为key ,修改如下:
function getElementKeyToUse(element: any, index?: number): Key {
if (
Array.isArray(element) ||
typeof element === 'string' ||
typeof element === 'number'
) {
return index;
}
return element.key !== null ? element.key : index;
}详见fix: fragment array没有key