react源码分析(10)-commit阶段中DOM节点的更新与插入

目录

2021SC@SDUSC

插入阶段

更新阶段

总结


2021SC@SDUSC

插入阶段

通过commitMutationEffectsOnFiber调用commitPlacement插入节点(称为子节点或目标节点),首先通过getHostParentFiber得到其DOM上的父节点,然后根据父节点的类型判断子节点所要插入的位置,此时可以重设子节点文字内容。找到before后可以执行插入方法。before节点是为了在插入节点时Fiber树的顺序不会因为映射到DOM上而改变,子节点必须插入在before节点的前面,如图所示

通过getHostSibling找到before节点,before节点与目标节点的相对位置可能有多重情况,左图只是情况的一种而已,这里给出react源码中的解决方法。

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;
  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;
    while (
      node.tag !== HostComponent &&
      node.tag !== HostText &&
      node.tag !== DehydratedFragment
    ) {
      // 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.flags & 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.flags & Placement)) {
      // Found it!
      return node.stateNode;
    }
  }
}

首先查看目标节点的兄弟节点是否为null,假如为null那么继续判断父节点是否为DOM节点(parent),是就直接插入(返回null值),否则继续向上寻找,当兄弟节点不为null时,判断是否为DOM节点,如果不是DOM节点,就判断是否为需要插入的节点,是的话直接continue继续寻找,不是的话寻找其子节点,没有子节点同样continue,有的话按照子节点遍历。如果兄弟节点是DOM节点,那就查看是否为需要插入的节点,是的话继续循环,不是的话就找到了before节点。另外,当要插入的节点并非DOM节点时,就需要寻找它所有的DOM子节点,注意,这里只插入所有分支的第一层子节点即可,因为这是自底向上的插入方式,底部DOM节点已经插入过其父节点。最终通过parent以及before节点完成了DOM节点的插入。

更新阶段

现在说一下DOM树的更新,通过commitMutationEffectsOnFiber调用commitWork,这其中文本只需要更新内容即可,而原生标签组件可以通过updateQueue来创建updatePayload并且通过commitUpdate以提交更新任务。

下面来详细看一下

   case HostComponent: {
      const instance: Instance = finishedWork.stateNode;
      if (instance != null) {
        // Commit the work prepared earlier.
        const newProps = finishedWork.memoizedProps;
        // For hydration we reuse the update path but we treat the oldProps
        // as the newProps. The updatePayload will contain the real change in
        // this case.
        const oldProps = current !== null ? current.memoizedProps : newProps;
        const type = finishedWork.type;
        // TODO: Type the updateQueue to be specific to host components.
        const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
        finishedWork.updateQueue = null;
        if (updatePayload !== null) {
          commitUpdate(
            instance,
            updatePayload,
            type,
            oldProps,
            newProps,
            finishedWork,
          );
        }
      }
      return;
    }
    case HostText: {
      invariant(
        finishedWork.stateNode !== null,
        'This should have a text node initialized. This error is likely ' +
          'caused by a bug in React. Please file an issue.',
      );
      const textInstance: TextInstance = finishedWork.stateNode;
      const newText: string = finishedWork.memoizedProps;
      // For hydration we reuse the update path but we treat the oldProps
      // as the newProps. The updatePayload will contain the real change in
      // this case.
      const oldText: string =
        current !== null ? current.memoizedProps : newText;
      commitTextUpdate(textInstance, oldText, newText);
      return;
    }

在HostComponent的情况下,首先取出内部实例作为DOM节点,然后从中取出参数,接着从以前的current节点中取出旧参数,接着获取任务节点内部的更新队列,然后执行commitUpdate方法。在commitUpdate中的updateProperties方法会对DOM节点的属性进行更新,源码如下:

function updateDOMProperties(
  domElement,
  updatePayload,
  wasCustomComponentTag,
  isCustomComponentTag
) {
  // TODO: Handle wasCustomComponentTag
  for (var i = 0; i < updatePayload.length; i += 2) {
    var propKey = updatePayload[i];
    var propValue = updatePayload[i + 1];

    if (propKey === STYLE) {
      setValueForStyles(domElement, propValue);
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      setInnerHTML(domElement, propValue);
    } else if (propKey === CHILDREN) {
      setTextContent(domElement, propValue);
    } else {
      setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
    }
  }
}

在这其中,updatePayload会被以[k1,v1,k2,v2]的形式循环取出并且分为STYLE,INNERHTML,TEXTCONTENT以及其他属性进行更新。

至于对HostText的更新,在这种情况下会直接调用commitTextUpdate方法

function commitTextUpdate(textInstance, oldText, newText) {
  textInstance.nodeValue = newText;
}

这种情况下会直接将实例的nodeValue赋值为newText。

总结

大致上介绍了react中DOM节点的插入(重点在before节点的寻找)以及更新的内部流程,分析了几个更新的具体例子。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值