commitPlacement 插入节点
这里比较复杂,因为Fiber树并不是跟DOM一一对应,比如组件是没有对应的DOM的; 按步骤划分,主要做了一下三件事:
- 从当前的处理的节点从上查找对应的真实DOM节点(因为Fiber树中存在组件Fiber的父节点)
- 找到当前处理的DOM节点要插入到具体哪个DOM节点之前,没有的话就append到父节点中
- 在插入的过程中,如果节点是组件,并不一定是真实的DOM,会优先兄弟节点,在到父节点,查找真实的DOM插入到父节点
typescript
function commitPlacement(finishedWork: Fiber): void {
if (!supportsMutation) {
return;
}
//step1 找到真实DOM父节点对应的Fiber
const parentFiber = getHostParentFiber(finishedWork);
let parent;
let isContainer;
switch (parentFiber.tag) {
case HostComponent:
parent = parentFiber.stateNode;
isContainer = false;
break;
case HostRoot:
parent = parentFiber.stateNode.containerInfo;
isContainer = true;
break;
}
//step2 找到当前节点要插入在哪个真实的DOM节点之前的那个节点
const before = getHostSibling(finishedWork);
let node: Fiber = finishedWork;
while (true) {
// 如果当前节点是一个真实的DOM节点或者文本节点
if (node.tag === HostComponent || node.tag === HostText) {
// 首先判断插到before节点之前的这个before节点存在不存在
if (before) {
// 如果这个before节点存在,并且当前节点还是一个容器节点
if (isContainer) {
insertInContainerBefore(parent, node.stateNode, before);
} else {
insertBefore(parent, node.stateNode, before);
}
} else {
if (isContainer) {
appendChildToContainer(parent, node.stateNode);
} else {
appendChild(parent, node.stateNode);
}
}
} else if (node.child !== null) {
// 当前节点不是真实DOM节点的Fiber对象,并且子节点存在
// 继续循环,找到子节点的真实DOM节点,将它插入父节点
node.child.return = node;
node = node.child;
continue;
}
// 说明当前节点插入完了 直接返回
if (node === finishedWork) {
return;
}
// 当前节点是组件节点,并且没有child, 会查找兄弟节点, 然后查找父节点
while (node.sibling === null) {
if (node.return === null || node.return === finishedWork) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}getHostParentFiber
找到真实DOM父节点
typescript
function isHostParent(fiber: Fiber): boolean {
return (
fiber.tag === HostComponent ||
fiber.tag === HostRoot ||
fiber.tag === HostPortal
);
}
function getHostParentFiber(fiber: Fiber): Fiber {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
}getHostSibling
找到相邻的真实DOM节点
typescript
function getHostSibling(fiber: Fiber): ?Instance {
let node: Fiber = fiber;
siblings: while (true) {
// 这个while循环的意思就是找到第一个存在兄弟节点的Fiber就跳出while循环
// 不管这个兄弟节点是不是真实的DOM节点
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// 到了host parent节点或者root根节点,还没有找到有兄弟节点的Fiber
// 只能退出循环了
return null;
}
node = node.return;
}
node.sibling.return = node.return;
// 指针指向当前节点的兄弟节点
node = node.sibling;
// 如果当前找到的兄弟节点不是一个具有真实DOM的Fiber节点
// 那么就要向下查找当前节点的Child节点,直到找到真实的DOM节点
// 或者找不到跳出循环
while (
node.tag !== HostComponent &&
node.tag !== HostText &&
node.tag !== DehydratedSuspenseComponent
) {
if (node.effectTag & Placement) {
// 如果当前节点也是需要插入的,那么久继续当前大的循环,尝试找当前节点的兄弟节点
continue siblings;
}
// 如果当前节点没有孩子节点,那就只能尝试兄弟节点找了
// 跳出本轮循环,进入大的循环,继续查找兄弟节点
if (node.child === null || node.tag === HostPortal) {
continue siblings;
} else {
// 当前节点具有孩子节点,就查找当前节点的孩子节点,
// 继续循环,查找真实的DOM节点
node.child.return = node;
node = node.child;
}
}
// 跳出上面的循环后,说明找到了一个真实的节点,
// 如果当前节点不需要插入,说明找到了我们想要的那个真实的DOM节点
// 否则 还得继续大循环,查找当前节点的兄弟节点
if (!(node.effectTag & Placement)) {
// Found it!
return node.stateNode;
}
}
}范例代码
比如我有如下jsx的代码:
typescript
class List extends React.Component {
render() {
return (
<ul>{this.props.list.map(item => <li key={item}>{item}</li>)}</ul>
)
}
}
class ItemChild extends React.Component {
render() {
return null
}
}
class Item extends React.Component {
render() {
return (
<>
<ItemChild />
<div>item-com</div>
</>
)
}
}
export default class App extends React.Component {
constructor() {
super();
this.state = {
list: ['a', 'b', 'c', 'd'],
visible: false
}
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({
list: ['a', 'c', 'd', 'b', 'f'],
visible: true
})
}
render() {
const { list } = this.state
return (
<div>
<List list={list} />
{
this.state.visible ? <Item /> : null
}
<button onClick={this.handleClick}>change</button>
</div>
)
}
}点击按钮的时候,List组件中的b节点会移动到最后,然后追加一个f节点, Item组件会被渲染
产生的需要变更的finishedWork的链表(也就是firstEffect到lastEffect)情况是:

当前代码执行得Fiber树结构:

b节点的移动过程
- 找到b节点的真实DOM父节点 ul
- 查找b节点需要插入的before节点(未找到,因为在f不存在之前, b需要插在最后一个)
- 调用ul.appendChild将b插入到最后
f节点的移动过程
- 找到f节点的真实DOM父节点 ul
- 查找f节点需要插入的before节点(未找到, f需要插在最后一个)
- 调用ul.appendChild将f插入到最后
Item组件的插入过程
- 找到Item组件的真实DOM节点div
- 找到需要插入before的真实DOM节点button
- 因为Item不是DOM节点对应的Fiber,所以会查找child节点,也就是ItemChild
- 发现ItemChild也不是DOM节点对应的Fiber,并且还没有child, 所以只能查找ItemChild的sibling
- 找到ItemChild的sibling是div节点,然后将当前div节点插入到button节点之前
App
App组件会走到Update过程,针对class组件的Update过程,什么都不需要做