commitPlacement
1 )概述
- 在 react commit 阶段的 commitRoot 第二个while循环中
- 调用了 commitAllHostEffects,在这个函数内部处理了
- 把一个新的dom节点挂载到真正的dom树上面去的一个过程
- 现在主要关注下其中调用的
commitPlacement
2 )源码
定位到 packages/react-reconciler/src/ReactFiberCommitWork.js#L850
js
function commitPlacement(finishedWork: Fiber): void {
if (!supportsMutation) {
return;
}
// Recursively insert all host nodes into the parent.
// 找到第一个父节点上的 HostComponent 或 HostRoot 或 HostPortal
const parentFiber = getHostParentFiber(finishedWork);
// Note: these two variables *must* always be updated together.
let parent;
let isContainer;
// 判断 tag 匹配处理程序
switch (parentFiber.tag) {
case HostComponent:
parent = parentFiber.stateNode;
isContainer = false;
break;
case HostRoot:
parent = parentFiber.stateNode.containerInfo;
isContainer = true;
break;
case HostPortal:
parent = parentFiber.stateNode.containerInfo;
isContainer = true;
break;
default:
invariant(
false,
'Invalid host parent fiber. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
}
if (parentFiber.effectTag & ContentReset) {
// Reset the text content of the parent before doing any insertions
resetTextContent(parent);
// Clear ContentReset from the effect tag
parentFiber.effectTag &= ~ContentReset;
}
// before可能不存在,比如如果是单一节点
const before = getHostSibling(finishedWork);
// We only have the top Fiber that was inserted but we need recurse down its
// children to find all the terminal nodes.
let node: Fiber = finishedWork;
while (true) {
// 只有这种情况下,才有插入dom的需要,根据 before 和 isContainer来执行不同的插入
if (node.tag === HostComponent || node.tag === HostText) {
if (before) {
// 这里匹配 HostRoot 或 HostPortal
if (isContainer) {
insertInContainerBefore(parent, node.stateNode, before);
} else {
// 这里匹配 HostComponent
insertBefore(parent, node.stateNode, before);
}
} else {
// 不存在 before
// 匹配 HostRoot 或 HostPortal
if (isContainer) {
appendChildToContainer(parent, node.stateNode);
} else {
// 匹配 HostComponent
appendChild(parent, node.stateNode);
}
}
} else if (node.tag === HostPortal) {
// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
// 如果都不符合,并且存在子节点,往下去找
node.child.return = node;
node = node.child;
continue; // 拿到 child 后,跳过此次,继续while循环
}
// 到终点了,就结束
if (node === finishedWork) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === finishedWork) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
-
进入
getHostParentFiber
jsfunction getHostParentFiber(fiber: Fiber): Fiber { let parent = fiber.return; while (parent !== null) { if (isHostParent(parent)) { return parent; } parent = parent.return; } invariant( false, 'Expected to find a host parent. This error is likely caused by a bug ' + 'in React. Please file an issue.', ); } function isHostParent(fiber: Fiber): boolean { return ( fiber.tag === HostComponent || fiber.tag === HostRoot || fiber.tag === HostPortal ); }
- 显然意见,这个循环就是向上查找到第一个
HostComponent
或HostRoot
或HostPortal
- 显然意见,这个循环就是向上查找到第一个
-
之后,判断
parentFiber.tag
,对不同条件的isContainer
进行赋值 -
之后,判断
ContentReset
是否存在,存在则对文本节点进行替换 -
进入
getHostSibling
向上找到 sibling 节点,下面这个英文注释留着 这个是查找sibling的核心算法jsfunction getHostSibling(fiber: Fiber): ?Instance { // We're going to search forward into the tree until we find a sibling host // node. Unfortunately, if multiple insertions are done in a row we have to // search past them. This leads to exponential search for the next sibling. // TODO: Find a more efficient way to do this. let node: Fiber = fiber; // 这里定义一个while循环叫做 siblings siblings: while (true) { // If we didn't find anything, let's try the next sibling. // 没有兄弟节点 while (node.sibling === null) { if (node.return === null || isHostParent(node.return)) { // If we pop out of the root or hit the parent the fiber we are the // last sibling. return null; } node = node.return; // 向父级溯源 } node.sibling.return = node.return; // 这里本就相同 node = node.sibling; // 兄弟节点变成当前 // 这个循环要找兄弟节点中的第一个dom节点 // 如果兄弟节点不是 HostComponent 或 HostText 往下去找 // 子节点中的第一个dom节点 while (node.tag !== HostComponent && node.tag !== HostText) { // If it is not host node and, we might have a host node inside it. // Try to search down until we find one. // 当前是要替换的节点,就没有必要向下找了 if (node.effectTag & Placement) { // If we don't have a child, try the siblings instead. continue siblings; } // If we don't have a child, try the siblings instead. // We also skip portals because they are not part of this host tree. // 像是这种,也没有必要继续找了 if (node.child === null || node.tag === HostPortal) { continue siblings; } else { node.child.return = node; node = node.child; } } // Check if this host node is stable or about to be placed. if (!(node.effectTag & Placement)) { // Found it! return node.stateNode; } } }
-
接下去又进入一个while循环
- 里面的第一个判断,
node.tag === HostComponent || node.tag === HostText
- 只有 HostComponent 和 HostText 才有插入dom的需要
- 注意,这里一系列的 if-else 是操作dom的核心
- 里面的第一个判断,
-
react真正展现给用户的是一棵dom树,而react中存储的是fiber树
-
而fiber树不会有每个节点对应的dom节点
-
以上是操作 commitPlacement 的源码处理,主要关注的是上述while循环和判断