React16源码: React中commitAllHostEffects内部的commitPlacement源码实现

commitPlacement


1 )概述

  • 在 react commit 阶段的 commitRoot 第二个while循环中
  • 调用了 commitAllHostEffects,在这个函数内部处理了
  • 把一个新的dom节点挂载到真正的dom树上面去的一个过程
  • 现在主要关注下其中调用的 commitPlacement

2 )源码

定位到 packages/react-reconciler/src/ReactFiberCommitWork.js#L850

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
    function 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
      );
    }
    
    • 显然意见,这个循环就是向上查找到第一个 HostComponentHostRootHostPortal
  • 之后,判断 parentFiber.tag,对不同条件的 isContainer 进行赋值
  • 之后,判断 ContentReset 是否存在,存在则对文本节点进行替换
  • 进入 getHostSibling 向上找到 sibling 节点,下面这个英文注释留着 这个是查找sibling的核心算法
    function 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循环和判断

你可能感兴趣的:(React,React,Native,react.js,前端,前端框架)