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

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

    js 复制代码
    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的核心算法

    js 复制代码
    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循环和判断

相关推荐
yngsqq2 分钟前
c#使用高版本8.0步骤
java·前端·c#
Myli_ing36 分钟前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风39 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
软件小伟1 小时前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾1 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧1 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
asleep7012 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm2 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王2 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue