React16源码: React中commit阶段的commitAllHostEffects的源码实现

commitAllHostEffects


1 )概述

  • 在 react commit 阶段的 commitRoot 第二个while循环中
  • 调用了 commitAllHostEffects
  • 现在来看下,里面发生了什么

2 )源码

回到 commit 阶段的第二个循环中,在 commitRoot 函数里

定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L690

看这里

nextEffect = firstEffect;
startCommitHostEffectsTimer();

// 第二个循环调用的方法是 commitAllHostEffects
// 这个就是主要是操作对于 dom 节点它需要去做的一些内容
// 比如说如果这个 dom 节点是刚刚新增的,那么要执行一个插入的操作
// 如果这个 dom 节点它需要被删除,我们要执行删除的操作
// 如果这个 dom 节点它的属性或者它 children 有更新,那么要执行一个更新的操作
while (nextEffect !== null) {
  let didError = false;
  let error;
  if (__DEV__) {
    invokeGuardedCallback(null, commitAllHostEffects, null);
    if (hasCaughtError()) {
      didError = true;
      error = clearCaughtError();
    }
  } else {
    try {
      commitAllHostEffects();
    } catch (e) {
      didError = true;
      error = e;
    }
  }
  if (didError) {
    invariant(
      nextEffect !== null,
      'Should have next effect. This error is likely caused by a bug ' +
        'in React. Please file an issue.',
    );
    captureCommitPhaseError(nextEffect, error);
    // Clean-up
    if (nextEffect !== null) {
      nextEffect = nextEffect.nextEffect;
    }
  }
}
stopCommitHostEffectsTimer();

resetAfterCommit(root.containerInfo);

注意,在执行新的循环之前,都要执行 nextEffect = firstEffect;
因为每一个 effect 对象上面可能有不同的各种 effectTag
所以它们都是要再次经历一个循环之后去判断是否要进行对应的操作的
所以每一个循环开始之前,都要重新赋值成单项链表的开头

上面是使用的地方,现在定位到 commitAllHostEffects

// https://github.com/facebook/react/blob/v16.6.3/packages/react-reconciler/src/ReactFiberScheduler.js#L392
function commitAllHostEffects() {
  while (nextEffect !== null) {
    if (__DEV__) {
      ReactCurrentFiber.setCurrentFiber(nextEffect);
    }
    recordEffect();

    const effectTag = nextEffect.effectTag;

    if (effectTag & ContentReset) {
      commitResetTextContent(nextEffect);
    }

    // 这个先跳过
    if (effectTag & Ref) {
      const current = nextEffect.alternate;
      if (current !== null) {
        commitDetachRef(current);
      }
    }

    // The following switch statement is only concerned about placement,
    // updates, and deletions. To avoid needing to add a case for every
    // possible bitmap value, we remove the secondary effects from the
    // effect tag and switch on that value.
    let primaryEffectTag = effectTag & (Placement | Update | Deletion);
    switch (primaryEffectTag) {
      case Placement: {
        commitPlacement(nextEffect);
        // Clear the "placement" from effect tag so that we know that this is inserted, before
        // any life-cycles like componentDidMount gets called.
        // TODO: findDOMNode doesn't rely on this any more but isMounted
        // does and isMounted is deprecated anyway so we should be able
        // to kill this.
        nextEffect.effectTag &= ~Placement;
        break;
      }
      // 先 Placement 后 Update
      case PlacementAndUpdate: {
        // Placement
        commitPlacement(nextEffect);
        // Clear the "placement" from effect tag so that we know that this is inserted, before
        // any life-cycles like componentDidMount gets called.
        nextEffect.effectTag &= ~Placement; // 在 处理完 Placement 之后,就去掉 Placement

        // Update
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      case Update: {
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      case Deletion: {
        commitDeletion(nextEffect);
        break;
      }
    }
    nextEffect = nextEffect.nextEffect;
  }

  if (__DEV__) {
    ReactCurrentFiber.resetCurrentFiber();
  }
}
  • 首先判断了一个叫 effectTag 是否有 ContentReset

    • 如果内部有文字的节点,文字节点需要去重置它的一个文字的内容
    • 这里调用了 commitResetTextContent, 这个方法来自于 ReactFiberCommitWork.js
      function commitResetTextContent(current: Fiber) {
        if (!supportsMutation) {
          return;
        }
        resetTextContent(current.stateNode);
      }
      
      // packages/react-dom/src/client/ReactDOMHostConfig.js#L339
      export function resetTextContent(domElement: Instance): void {
        setTextContent(domElement, '');
      }
      
      • 进入 setTextContent
        /**
         * Copyright (c) Facebook, Inc. and its affiliates.
        *
        * This source code is licensed under the MIT license found in the
        * LICENSE file in the root directory of this source tree.
        *
        * @flow
        */
        
        import {TEXT_NODE} from '../shared/HTMLNodeType';
        
        /**
         * Set the textContent property of a node. For text updates, it's faster
        * to set the `nodeValue` of the Text node directly instead of using
        * `.textContent` which will remove the existing node and create a new one.
        *
        * @param {DOMElement} node
        * @param {string} text
        * @internal
        */
        let setTextContent = function(node: Element, text: string): void {
          if (text) {
            let firstChild = node.firstChild;
            // 这里,一个first === last 代表只有一个节点,才会设置,防止有其他节点导致误删除了其他节点
            if (
              firstChild &&
              firstChild === node.lastChild &&
              firstChild.nodeType === TEXT_NODE
            ) {
              firstChild.nodeValue = text;
              return;
            }
          }
          node.textContent = text;
        };
        
        export default setTextContent;
        
        • 这个方法,就是给一个节点设置文本内容
  • 接下去,primaryEffectTag

    • 它最主要的对dom节点来说,它要执行的就是 Placement, Update, Deletion 这三种操作
    • 只有 Placement 的节点大概率是新增的节点,直接进行插入
    • 匹配 PlacementAndUpdate 可能是之前存在的节点,需要和兄弟节点进行位置交换,或需要更新
    • 只有 Update, 执行 commitWork
    • 只有 Deletion, 执行 commitDeletion
  • 接下去就是赋值 nextEffect,然后再次进入这个循环

  • 直到我们整个单链表循环完成之后,它就结束了

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