在构建一个简易版 React 框架的过程中,我们常常会遇到一些边缘情况(edge case),本次课程聚焦于其中一种常见但容易忽略的情况:条件渲染返回 false 时导致 Fiber 构建报错的问题。以下是详细分析与修复过程。
🧩 问题复现
我们从一个组件 Counter
开始:
let showBar = false;
function Counter({ num }) {
const bar = <div>bar</div>;
function handleShowBar() {
showBar = !showBar;
React.update();
}
return (
<div>
Counter
<button onClick={handleShowBar}>showBar</button>
{showBar && bar}
</div>
);
}
初看之下并无问题,但运行时报错。我们追踪到 createElement
函数中的调试信息:
console.log('child 111', child)
打印发现 child
为 false
,这是因为 JSX 中 showBar && bar
在 showBar === false
时会返回布尔值 false
,而我们的框架没有处理布尔类型子节点。
✅ 问题定位与第一次修复
原先的处理逻辑只考虑了文本节点和对象节点:
const isTextNode = typeof child === "string" || typeof child === "number"
return isTextNode ? createTextNode(child) : child
我们增加一个布尔值的处理判断(过滤掉 false
, null
, undefined
等):
children: children
.filter(child => child != null && child !== false)
.map(child => {
const isTextNode = typeof child === "string" || typeof child === "number";
return isTextNode ? createTextNode(child) : child;
})
或在 reconcileChildren
中增加:
if (!child) return;
这样,当 showBar
为 false
时,不再处理布尔值。
🔄 问题延伸:变更顺序导致报错
我们继续尝试调整元素顺序:
return (
<div>
Counter
{showBar && bar}
<button onClick={handleShowBar}>showBar</button>
</div>
);
此时再次报错,错误原因是 button
组件在当前 Fiber Tree 中找不到旧的对应节点。追踪代码,发现问题出在 prevChild
的赋值:
if (index === 0) {
fiber.child = newFiber;
} else {
prevChild.sibling = newFiber; // ❗ newFiber 可能是 undefined
}
prevChild = newFiber;
修复方法:确保只有 newFiber
存在时才赋值给 prevChild
:
if (newFiber) {
if (index === 0) {
fiber.child = newFiber;
} else if (prevChild) {
prevChild.sibling = newFiber;
}
prevChild = newFiber;
}
这样处理后,无论组件顺序如何调整,Fiber 树都能正确生成。
✅ 最终修复后的 reconcileChildren
关键逻辑
function reconcileChildren(fiber, children) {
let oldFiber = fiber.alternate?.child;
let prevChild = null;
children.forEach((child, index) => {
if (!child) return;
const isSameType = oldFiber && oldFiber.type === child.type;
let newFiber;
if (isSameType) {
newFiber = {
type: child.type,
props: child.props,
dom: oldFiber.dom,
parent: fiber,
alternate: oldFiber,
effectTag: 'update',
child: null,
sibling: null,
};
} else {
newFiber = {
type: child.type,
props: child.props,
dom: null,
parent: fiber,
effectTag: 'placement',
child: null,
sibling: null,
};
if (oldFiber) deletions.push(oldFiber);
}
if (oldFiber) oldFiber = oldFiber.sibling;
if (newFiber) {
if (index === 0) {
fiber.child = newFiber;
} else if (prevChild) {
prevChild.sibling = newFiber;
}
prevChild = newFiber;
}
});
while (oldFiber) {
deletions.push(oldFiber);
oldFiber = oldFiber.sibling;
}
}