Skip to content
目录

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)情况是:

fleffect.png

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

fleffect.png

b节点的移动过程

  1. 找到b节点的真实DOM父节点 ul
  2. 查找b节点需要插入的before节点(未找到,因为在f不存在之前, b需要插在最后一个)
  3. 调用ul.appendChild将b插入到最后

f节点的移动过程

  1. 找到f节点的真实DOM父节点 ul
  2. 查找f节点需要插入的before节点(未找到, f需要插在最后一个)
  3. 调用ul.appendChild将f插入到最后

Item组件的插入过程

  1. 找到Item组件的真实DOM节点div
  2. 找到需要插入before的真实DOM节点button
  3. 因为Item不是DOM节点对应的Fiber,所以会查找child节点,也就是ItemChild
  4. 发现ItemChild也不是DOM节点对应的Fiber,并且还没有child, 所以只能查找ItemChild的sibling
  5. 找到ItemChild的sibling是div节点,然后将当前div节点插入到button节点之前

App

App组件会走到Update过程,针对class组件的Update过程,什么都不需要做

备案号: 浙ICP备2023000081号