今天带大家实现节点删除。
先看个例子。
js
function FunctionComponent() {
const [count1, setCount1] = useReducer((x) => x + 1, 0);
return (
<div>
{
count1 % 2 === 0 ? (
<button onClick={() => {setCount1()}}>{count1}</button>
) : (<span>react</span>)
}
</div>
)
}
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render((<FunctionComponent />) as any);
思路:
- 节点删除包含两个部分:vdom 删除(不用做),dom 删除
- 如何删除 dom?把所有要删除的节点放在父 fiber 上
Render 阶段
修改 reconcileSingleElement
函数,实现删除单个节点 deleteChild
和删除多个节点 deleteRemainingChildren
。
js
function deleteChild(returnFiber: Fiber, childToDelete: Fiber) {
// 初次渲染
if (!shouldTrackSideEffects) {
return;
}
// 把所有要删除的节点放在父 fiber 上
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
returnFiber.deletions!.push(childToDelete);
}
}
// 删除多个节点
function deleteRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber | null
) {
if (!shouldTrackSideEffects) {
return;
}
// 初始值
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
// 协调单个节点,对于页面初次渲染,创建 fiber,不涉及对比复用老节点
// new (1)
// old 2 [1] 3 4
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement
) {
// 节点复用的条件
// 1. 同一层级下 2. key 相同 3. 类型相同
// element 和 currentFirstChild 对应同一个父级,第一个条件满足
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
if (child.key === key) {
const elementType = element.type;
// 可以复用
if (child.elementType === elementType) {
// todo 后面其它 fiber 可以删除了
const existing = useFiber(child, element.props);
existing.return = returnFiber;
return existing;
} else {
// 前提:React 不认为同一层级下有两个相同的 key 值
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
// 删除单个节点
deleteChild(returnFiber, child);
}
// 老 fiber 节点是单链表
child = child.sibling;
}
let createdFiber = createFiberFromElement(element);
createdFiber.return = returnFiber;
return createdFiber;
}
Commit 阶段
commitReconciliationEffects
增加删除的 case。
js
// fiber.stateNode 是 DOM 节点
export function isHost(fiber: Fiber): boolean {
return fiber.tag === HostComponent || fiber.tag === HostText;
}
// 获取子 dom 节点
function getStateNode(fiber: Fiber) {
let node = fiber;
while (1) {
if (isHost(node) && node.stateNode) {
return node.stateNode;
}
node = node.child as Fiber;
}
}
// 根据 fiber 删除 dom 节点,必须有父 dom、子 dom
// 删除多个 dom 节点
function commitDeletions(
deletions: Array<Fiber>,
parentDOM: Element | Document | DocumentFragment
) {
deletions.forEach((deletion) => {
parentDOM.removeChild(getStateNode(deletion));
});
}
function commitReconciliationEffects(finishedWork: Fiber) {
const flags = finishedWork.flags;
if (flags & Placement) {
// 页面初次渲染 新增插入 appendChild
// todo 页面更新,修改位置 appendChild || insertBefore
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
// 删除
if (flags & ChildDeletion) {
// parentFiber 是 deletions 的父 dom 对应的 fiber
const parentFiber = isHostParent(finishedWork)
? finishedWork
: getHostParentFiber(finishedWork);
const parentDOM = parentFiber.stateNode;
commitDeletions(finishedWork.deletions!, parentDOM);
finishedWork.flags &= ~ChildDeletion;
finishedWork.deletions = null;
}
}