lz-lee / React-Source-Code

React源码阅读笔记
11 stars 0 forks source link

commitRoot #12

Open lz-lee opened 4 years ago

lz-lee commented 4 years ago

commitRoot

第一次循环 commitBeforeMutationLifecycles

// 只有 classComponent 才会调用 getSnapshotBeforeUpdate
function commitBeforeMutationLifeCycles(
  current: Fiber | null,
  finishedWork: Fiber,
): void {
  switch (finishedWork.tag) {
    case ClassComponent: {
      if (finishedWork.effectTag & Snapshot) {
        // 非首次渲染
        if (current !== null) {
          // 老 props 
          const prevProps = current.memoizedProps;
          // 老 state 
          const prevState = current.memoizedState;
          startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
          // 组件实例
          const instance = finishedWork.stateNode;
          instance.props = finishedWork.memoizedProps;
          instance.state = finishedWork.memoizedState;
          // 获取快照, getSnapshotBeforeUpdate 方法里可以调用 this 
          const snapshot = instance.getSnapshotBeforeUpdate(
            prevProps,
            prevState,
          );
          if (__DEV__) {}
          // 获取的快照挂载在 instance 上,componentDidUpdate 方法的第三个参数即 snapshot
          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
          stopPhaseTimer();
        }
      }
      return;
    }
    case HostRoot:
    case HostComponent:
    case HostText:
    case HostPortal:
    case IncompleteClassComponent:
      // Nothing to do for these component types
      return;
    default: {}
  }
}

第二次循环 commitAllHostEffects

}


### commitResetTextContent 文字节点设置为空字符串
```js
function commitResetTextContent(current: Fiber) {
  if (!supportsMutation) {
    return;
  }
  resetTextContent(current.stateNode);
}
// resetTextContent
function resetTextContent(domElement: Instance): void {
  setTextContent(domElement, '');
}

// setTextContent 设置文本内容
setTextContent = function(node: Element, text: string): void {
  if (text) {
    let firstChild = node.firstChild;

    if (
      firstChild &&
      firstChild === node.lastChild &&
      firstChild.nodeType === TEXT_NODE
    ) {
      firstChild.nodeValue = text;
      return;
    }
  }
  node.textContent = text;
};

commitDetachRef 清空ref

function commitDetachRef(current: Fiber) {
  const currentRef = current.ref;
  if (currentRef !== null) {
    if (typeof currentRef === 'function') {
      currentRef(null);
    } else {
      currentRef.current = null;
    }
  }
}

Placement 插入:commitPlacement

function commitPlacement(finishedWork: Fiber): void {
  // ReactDOM 调用 supportsMutation 默认为 true 
  if (!supportsMutation) {
    return;
  }

  // Recursively insert all host nodes into the parent.
  // 往上找到第一个节点类型是 原生dom节点、Root 节点、portal的父节点
  const parentFiber = getHostParentFiber(finishedWork);

  // Note: these two variables *must* always be updated together.
  let parent;
  // 是否为容器型
  let isContainer;

  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;
  }

  // 找需要使用 insertBefore 来插入的节点 , parent.insertBefore(newNode, beforeNode)
  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 节点 或者 文本节点
    if (node.tag === HostComponent || node.tag === HostText) {
      // insertBefore 插入
      if (before) {
        if (isContainer) {
          insertInContainerBefore(parent, node.stateNode, before);
        } else {
          insertBefore(parent, node.stateNode, before);
        }
      } else {
        // appendChild 
        if (isContainer) {
          appendChildToContainer(parent, node.stateNode);
        } else {
          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) {
      // 这里和 completeWork 里的 appendAllChildren 类似
      // 即节点不是 dom 节点(文本节点), 并且 node.child 不为 null,应该要遍历其子节点,其实就是 classComponent 
      node.child.return = node;
      node = node.child;
      continue;
    }
    if (node === finishedWork) {
      return;
    }
    // 深度优先遍历
    // 这个小循环找有兄弟节点的父节点,如果 sibling 不为 null 则会去遍历兄弟节点
    // 如果没有兄弟节点,继续往上找,直到没有父节点 或者 父节点为当前遍历的节点时停止循环
    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 isHostParent(fiber: Fiber): boolean { return ( fiber.tag === HostComponent || fiber.tag === HostRoot || fiber.tag === HostPortal ); }


#### getHostSibling 
- 找插入节点的参考节点: 即第一个类型是 `HostComponent` 或者 `HostText`  的兄弟节点
- 如果没有兄弟节点,则找往上找第一个是 `dom 节点` 的父节点,或者从父节点里的兄弟节点里找第一个 dom 节点
- 参考节点的 `effectTag` 不是 `placement` 才返回
```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.
  // fiber 是待插入(insertBefore 或者 appendChild )的那个节点,如果要使用 insertBefore 就要找到它的第一个原生 dom 节点的兄弟节点
  let node: Fiber = fiber;
  siblings: while (true) {
    // If we didn't find anything, let's try the next sibling.
    // 如果没有兄弟节点,则一直往上找父节点,直到找到父节点是 HostComponent、HostRoot、HostPortal这三种或者为null为止,即确定父节点肯定是原生 dom 节点
    // 或者从父节点的兄弟节点里找 dom 节点
    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;
    // 从兄弟节点开始找第一个原生 dom 节点
    node = node.sibling;
    // 如果不是原生 dom 节点 或者 text,那么就要去找它(classComponent)的 child 
    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.
      // 没有 child 或者是 portal 跳过
      if (node.child === null || node.tag === HostPortal) {
        continue siblings;
      } else {
        // 从 child 里找第一个 dom 节点
        node.child.return = node;
        node = node.child;
      }
    }
    // Check if this host node is stable or about to be placed.
    // 到这 node 肯定是原生 dom 节点了,并且是要没有标记插入的才算真正找到
    if (!(node.effectTag & Placement)) {
      // Found it!
      return node.stateNode;
    }
  }
}

insertBefore or appendChild

function insertBefore(
  parentInstance: Instance,
  child: Instance | TextInstance,
  beforeChild: Instance | TextInstance,
): void {
  parentInstance.insertBefore(child, beforeChild);
}

function appendChild(
  parentInstance: Instance,
  child: Instance | TextInstance,
): void {
  parentInstance.appendChild(child);
}

PlacementAndUpdate 插入和更新:commitPlacement、commitWork

Updates 更新:commitWork

// current 是老的节点
// newEffect 是新的节点
const current = nextEffect.alternate;
commitWork(current, nextEffect);

function commitWork(current: Fiber | null, finishedWork: Fiber): void {
  if (!supportsMutation) {
    commitContainer(finishedWork);
    return;
  }

  switch (finishedWork.tag) {
    case ClassComponent: {
      return;
    }
    case HostComponent: {
      const instance: Instance = finishedWork.stateNode;
      // 节点已经创建
      if (instance != null) {
        // Commit the work prepared earlier.
        // 新节点上的 props
        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.
        // 老节点上的 props
        const oldProps = current !== null ? current.memoizedProps : newProps;
        // dom 标签
        const type = finishedWork.type;
        // TODO: Type the updateQueue to be specific to host components.
        // updatePayload 为 diffProperties 是创建的 [k1, v1, k2, v2] 格式的需要更新的 prop 的数组
        const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
        finishedWork.updateQueue = null;
        if (updatePayload !== null) {
          commitUpdate(
            instance,
            updatePayload,
            type,
            oldProps,
            newProps,
            finishedWork,
          );
        }
      }
      return;
    }
    case HostText: {
      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;
        // 文本节点直接设置 textInstance.nodeValue = newText;
      commitTextUpdate(textInstance, oldText, newText);
      return;
    }
    case HostRoot: {
      return;
    }
    case Profiler: {
      return;
    }
    case SuspenseComponent: {
      return;
    }
    case IncompleteClassComponent: {
      return;
    }
    default: {
    }
  }
}

commitUpdate

updateProperties

updateDOMProperties

function commitDeletion(current: Fiber): void {
  if (supportsMutation) {
    // Recursively delete all host nodes from the parent.
    // Detach refs and call componentWillUnmount() on the whole subtree.
    // ReactDOM 只会走这个方法
    unmountHostComponents(current);
  } else {
    // Detach refs and call componentWillUnmount() on the whole subtree.
    commitNestedUnmounts(current);
  }
  // 清空 Fiber 上的属性
  detachFiber(current);
}

unmountHostComponents

function unmountHostComponents(current): void {
  // We only have the top Fiber that was deleted but we need recurse down its
  // children to find all the terminal nodes.
  let node: Fiber = current;

  // Each iteration, currentParent is populated with node's host parent if not
  // currentParentIsValid.
  let currentParentIsValid = false;

  // Note: these two variables *must* always be updated together.
  let currentParent;
  let currentParentIsContainer;

  while (true) {
    if (!currentParentIsValid) {
      // 找到 parent 是 HostComponent、HostRoot、HostPortal 类型的节点赋值给 currentParent
      let parent = node.return;
      findParent: while (true) {
        switch (parent.tag) {
          case HostComponent:
            currentParent = parent.stateNode;
            currentParentIsContainer = false;
            break findParent;
          case HostRoot:
            currentParent = parent.stateNode.containerInfo;
            currentParentIsContainer = true;
            break findParent;
          case HostPortal:
            currentParent = parent.stateNode.containerInfo;
            currentParentIsContainer = true;
            break findParent;
        }
        parent = parent.return;
      }
      currentParentIsValid = true;
    }

    if (node.tag === HostComponent || node.tag === HostText) {
      // 原生 dom 节点的子节点下 也有 classComponent 或者 portal,因此要递归调用遍历整个树的每个节点
      commitNestedUnmounts(node);
      // After all the children have unmounted, it is now safe to remove the
      // node from the tree.
      // 如果是原生 dom 节点,直接 removeChild 删除
      if (currentParentIsContainer) {
        removeChildFromContainer((currentParent: any), node.stateNode);
      } else {
        removeChild((currentParent: any), node.stateNode);
      }
      // Don't visit children because we already visited them.
    } else if (node.tag === HostPortal) {
      // When we go into a portal, it becomes the parent to remove from.
      // We will reassign it back when we pop the portal on the way up.
      // 如果是 HostPortal 继续循环子节点
      currentParent = node.stateNode.containerInfo;
      currentParentIsContainer = true;
      // Visit children because portals might contain host components.
      if (node.child !== null) {
        // 子节点不为 null 继续往下遍历
        node.child.return = node;
        node = node.child;
        continue;
      }
    } else {
      // 非 dom 节点 和非 portal ,即 classComponent
      commitUnmount(node);
      // Visit children because we may find more host components below.
      if (node.child !== null) {
        // 子节点不为 null 继续往下遍历
        node.child.return = node;
        node = node.child;
        continue;
      }
    }
    if (node === current) {
      return;
    }
    // 深度遍历,往上找 sibling
    while (node.sibling === null) {
      if (node.return === null || node.return === current) {
        return;
      }
      node = node.return;
      if (node.tag === HostPortal) {
        // When we go out of the portal, we need to restore the parent.
        // Since we don't keep a stack of them, we will search for it.
        currentParentIsValid = false;
      }
    }
    node.sibling.return = node.return;
    node = node.sibling;
  }
}

commitUnmount

// safelyDetachRef function safelyDetachRef(current: Fiber) { const ref = current.ref; if (ref !== null) { if (typeof ref === 'function') { if (DEV) } else { try { ref(null); } catch (refError) { captureCommitPhaseError(current, refError); } } } else { ref.current = null; } } }


#### commitNestedUnmounts
- `HostComponent` 执行卸载,原生 `dom 节点` 的子节点下也有 `classComponent` 或者 `portal` 
- 嵌套递归卸载子树达到卸载 `HostPortal` 的目的 
```js
function commitNestedUnmounts(root: Fiber): void {
  // While we're inside a removed host node we don't want to call
  // removeChild on the inner nodes because they're removed by the top
  // call anyway. We also want to call componentWillUnmount on all
  // composites before this host node is removed from the tree. Therefore
  // we do an inner loop while we're still inside the host node.
  let node: Fiber = root;
  while (true) {
    // 如果 node 是 portal (只会发生在 portal 为 原生 dom 节点的 sibling 的情况,如果要卸载的组件的第一个 child 就是 portal,那么就会在 unmountHostComponents 里循环卸载子节点了),-> commitUnmount -> unmountHostComponents -> portal 会去遍历 child -> child 为 hostComponent -> commitNestedUnmounts -> commitUnmount 卸载 child 
    // commitUnmount 卸载 child  -> portal 会执行 removeChildFromContainer -> 最后依次返回
    // 嵌套递归调用卸载节点,达到卸载 HostPortal 的目的
    commitUnmount(node);
    // Visit children because they may contain more composite or host nodes.
    // Skip portals because commitUnmount() currently visits them recursively.
    if (
      node.child !== null &&
      // If we use mutation we drill down into portals using commitUnmount above.
      // If we don't use mutation we drill down into portals here instead.
      (!supportsMutation || node.tag !== HostPortal)
    ) {
      // 不是 portal 并且有 child,继续循环
      node.child.return = node;
      node = node.child;
      continue;
    }
    if (node === root) {
      return;
    }
    // 深度遍历,往上找 sibling
    while (node.sibling === null) {
      if (node.return === null || node.return === root) {
        return;
      }
      node = node.return;
    }
    node.sibling.return = node.return;
    node = node.sibling;
  }
}

removeChildFromContainer & removeChild

function removeChild(
  parentInstance: Instance,
  child: Instance | TextInstance,
): void {
  parentInstance.removeChild(child);
}

function removeChildFromContainer(
  container: Container,
  child: Instance | TextInstance,
): void {
  if (container.nodeType === COMMENT_NODE) {
    (container.parentNode: any).removeChild(child);
  } else {
    container.removeChild(child);
  }
}

第三次循环 commitAllLifeCycles

commitAllLifeCycles

function commitLifeCycles(
  finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedExpirationTime: ExpirationTime,
): void {
  switch (finishedWork.tag) {
    case ClassComponent: {
      const instance = finishedWork.stateNode;
      if (finishedWork.effectTag & Update) {
        // 首次渲染
        if (current === null) {
          startPhaseTimer(finishedWork, 'componentDidMount');
          instance.props = finishedWork.memoizedProps;
          instance.state = finishedWork.memoizedState;
          instance.componentDidMount();
          stopPhaseTimer();
        } else {
          // 更新渲染
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          startPhaseTimer(finishedWork, 'componentDidUpdate');
          instance.props = finishedWork.memoizedProps;
          instance.state = finishedWork.memoizedState;
          instance.componentDidUpdate(
            prevProps, // setState 后产生的新的 props
            prevState, // setState 后产生的新的 state 
            instance.__reactInternalSnapshotBeforeUpdate, // Snapshot 快照
          );
          stopPhaseTimer();
        }
      }
      const updateQueue = finishedWork.updateQueue;
      // updateQueue 不为空,要执行 queue 里可能存在的 callback
      if (updateQueue !== null) {
        instance.props = finishedWork.memoizedProps;
        instance.state = finishedWork.memoizedState;
        commitUpdateQueue(
          finishedWork,
          updateQueue,
          instance,
          committedExpirationTime,
        );
      }
      return;
    }
    case HostRoot: {
      // Root 节点, ReactDOM.render 方法会创建一个update,这个方法可以接受的第三个参数就是 callback,在第一次渲染结束后调用
      const updateQueue = finishedWork.updateQueue;
      if (updateQueue !== null) {
        let instance = null;
        if (finishedWork.child !== null) {
          switch (finishedWork.child.tag) {
            case HostComponent:
              instance = getPublicInstance(finishedWork.child.stateNode);
              break;
            case ClassComponent:
              instance = finishedWork.child.stateNode;
              break;
          }
        }
        commitUpdateQueue(
          finishedWork,
          updateQueue,
          instance,
          committedExpirationTime,
        );
      }
      return;
    }
    case HostComponent: {
      const instance: Instance = finishedWork.stateNode;

      // Renderers may schedule work to be done after host components are mounted
      // (eg DOM renderer may schedule auto-focus for inputs and form controls).
      // These effects should only be committed when components are first mounted,
      // aka when there is no current/alternate.
      // 首次渲染且 effecttag 为 update,则会自动 focus 需要 autofocus 的节点
      if (current === null && finishedWork.effectTag & Update) {
        const type = finishedWork.type;
        const props = finishedWork.memoizedProps;
        // Button, Input, Select, Textarea 标签,props.autofocus 为真,则首次渲染会 focus
        commitMount(instance, type, props, finishedWork);
      }

      return;
    }
    case HostText: {
      // We have no life-cycles associated with text.
      return;
    }
    case HostPortal: {
      // We have no life-cycles associated with portals.
      return;
    }
    case Profiler: {
    }
    case SuspenseComponent: {
      if (finishedWork.effectTag & Callback) {
        // In non-strict mode, a suspense boundary times out by commiting
        // twice: first, by committing the children in an inconsistent state,
        // then hiding them and showing the fallback children in a subsequent
        // commit.
        const newState: SuspenseState = {
          alreadyCaptured: true,
          didTimeout: false,
          timedOutAt: NoWork,
        };
        finishedWork.memoizedState = newState;
        scheduleWork(finishedWork, Sync);
        return;
      }
      let oldState: SuspenseState | null =
        current !== null ? current.memoizedState : null;
      let newState: SuspenseState | null = finishedWork.memoizedState;
      let oldDidTimeout = oldState !== null ? oldState.didTimeout : false;

      let newDidTimeout;
      let primaryChildParent = finishedWork;
      if (newState === null) {
        newDidTimeout = false;
      } else {
        newDidTimeout = newState.didTimeout;
        if (newDidTimeout) {
          primaryChildParent = finishedWork.child;
          newState.alreadyCaptured = false;
          if (newState.timedOutAt === NoWork) {
            // If the children had not already timed out, record the time.
            // This is used to compute the elapsed time during subsequent
            // attempts to render the children.
            newState.timedOutAt = requestCurrentTime();
          }
        }
      }

      if (newDidTimeout !== oldDidTimeout && primaryChildParent !== null) {
        hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
      }
      return;
    }
    case IncompleteClassComponent:
      break;
    default: {}
  }
}

commitUpdateQueue

function commitUpdateQueue<State>(
  finishedWork: Fiber,
  finishedQueue: UpdateQueue<State>,
  instance: any,
  renderExpirationTime: ExpirationTime,
): void {
  // If the finished render included captured updates, and there are still
  // lower priority updates left over, we need to keep the captured updates
  // in the queue so that they are rebased and not dropped once we process the
  // queue again at the lower priority.
  // 如果有捕获错误的更新,即一次渲染过程中还没有完成的更新
  if (finishedQueue.firstCapturedUpdate !== null) {
    // Join the captured update list to the end of the normal list.
    if (finishedQueue.lastUpdate !== null) {
      // 如果还有普通的更新未完成(即低优先级的任务),将错误的更新加入到链表之后,将错误捕获的更新放到低优先级的任务上渲染
      finishedQueue.lastUpdate.next = finishedQueue.firstCapturedUpdate;
      finishedQueue.lastUpdate = finishedQueue.lastCapturedUpdate;
    }
    // Clear the list of captured updates.
    // 如果没有低优先级任务,则清空本次渲染过程产生的捕获错误更新
    finishedQueue.firstCapturedUpdate = finishedQueue.lastCapturedUpdate = null;
  }

  // Commit the effects
  // 调用 effect.callback, 一般是 setState 的回调函数
  commitUpdateEffects(finishedQueue.firstEffect, instance);
  finishedQueue.firstEffect = finishedQueue.lastEffect = null;

  commitUpdateEffects(finishedQueue.firstCapturedEffect, instance);
  finishedQueue.firstCapturedEffect = finishedQueue.lastCapturedEffect = null;
}

function commitUpdateEffects<State>(
  effect: Update<State> | null,
  instance: any,
): void {
  while (effect !== null) {
    const callback = effect.callback;
    if (callback !== null) {
      effect.callback = null;
      // 执行 callback
      callCallback(callback, instance);
    }
    effect = effect.nextEffect;
  }
}