lz-lee / React-Source-Code

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

completeUnitOfWork #11

Open lz-lee opened 4 years ago

lz-lee commented 4 years ago

回顾 performUnitOfWork

function performUnitOfWork(workInProgress: Fiber): Fiber | null {
  const current = workInProgress.alternate;

  // See if beginning this work spawns more work.
  startWorkTimer(workInProgress);
  let next;
  if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      startProfilerTimer(workInProgress);
    }
    // 执行对整个树每个节点进行更新的操作,赋值 next 为更新完后的下一个节点
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    // 将最新的 props 赋值给目前正在用的 props
    workInProgress.memoizedProps = workInProgress.pendingProps;

    if (workInProgress.mode & ProfileMode) {
      // Record the render duration assuming we didn't bailout (or error).
      stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
    }
  } else {
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
  }

  // 更新到最后的节点,叶子节点
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    // 有兄弟节点返回兄弟节点,否则返回null,往上遍历
    next = completeUnitOfWork(workInProgress);
  }

  ReactCurrentOwner.current = null;

  return next;
}

completeUnitOfWork

function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
  // Attempt to complete the current unit of work, then move to the
  // next sibling. If there are no more siblings, return to the
  // parent fiber.
  while (true) {
    // The current, flushed, state of this fiber is the alternate.
    // Ideally nothing should rely on this, but relying on it here
    // means that we don't need an additional field on the work in
    // progress.
    const current = workInProgress.alternate;
    // 父节点
    const returnFiber = workInProgress.return;
    // 兄弟节点
    const siblingFiber = workInProgress.sibling;

    // Incomplete 为这个节点出现错误并捕获的 effect
    // 逻辑与操作判断 workInProgress.effectTag 是否为 Incomplete
    // Incomplete & Incomplete => Incomplete
    // 非 Incomplete & Incomplete => NoEffect 即没错的情况
    if ((workInProgress.effectTag & Incomplete) === NoEffect) {
      // This fiber completed.
      if (enableProfilerTimer) {
        if (workInProgress.mode & ProfileMode) {
          startProfilerTimer(workInProgress);
        }

        nextUnitOfWork = completeWork(
          current,
          workInProgress,
          nextRenderExpirationTime,
        );

        if (workInProgress.mode & ProfileMode) {
          // Update render duration assuming we didn't error.
          stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
        }
      } else {
        nextUnitOfWork = completeWork(
          current,
          workInProgress,
          nextRenderExpirationTime,
        );
      }
      // 更新都是从 root 节点开始,需要同步子树中更新优先级最高的 expirationTime 到 root 上
      // 完成一次节点更新后,需要更新 childExpirationTime
      // 重置 childExpirationTime (子节点当中更新优先级最高的对应的 expirationTime )
      stopWorkTimer(workInProgress);
      resetChildExpirationTime(workInProgress, nextRenderExpirationTime);

      if (
        returnFiber !== null &&
        // Do not append effects to parents if a sibling failed to complete
        // 没有错误才会更新, 有错误的不会渲染子节点的
        (returnFiber.effectTag & Incomplete) === NoEffect 
      ) {
        // Append all the effects of the subtree and this fiber onto the effect
        // list of the parent. The completion order of the children affects the
        // side-effect order.
        // 处理父节点的 effect
        // 真正要做的是把当前节点的 firstEffect 到 lastEffect (即当前节点的子节点的 effect ) 单向链表挂载到它的父节点的同样的位置

        // 表示 returnFiber 还未记录任何有副作用(effect)的子节点
        if (returnFiber.firstEffect === null) {
          // 当前节点的 firstEffect 赋值给 returnFiber
          returnFiber.firstEffect = workInProgress.firstEffect;
        }
        if (workInProgress.lastEffect !== null) {
          // 如果 returnFiber 已经有记录过别的 side effect 节点,当前节点的 firstEffect 则挂载在单向链表的最后
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
          }
          // 更新 lastEffect
          returnFiber.lastEffect = workInProgress.lastEffect;
        }

        // 处理当前节点的 effect
        // 当前节点也可能有 effect
        const effectTag = workInProgress.effectTag;
        // Skip both NoWork and PerformedWork tags when creating the effect list.
        // PerformedWork effect is read by React DevTools but shouldn't be committed.
        // 如果当前节点有 effect 且大于 1, PerformeWork 值为 1 给 devtool 用的,则将当前节点挂载到父节点的 side effect 链的最后一个
        if (effectTag > PerformedWork) {
          // 如果 returnFiber 有记录过别的 effect
          if (returnFiber.lastEffect !== null) {
            // 更新最后一个的 next
            returnFiber.lastEffect.nextEffect = workInProgress;
          } else {
            // 否则为第一个 effect
            returnFiber.firstEffect = workInProgress;
          }
          // 更新 lastEffect
          returnFiber.lastEffect = workInProgress;
        }
        // 一层一层往上,最终在 RootFiber 上赋值的 firstEffect 到 lastEffect 这个单向链表结构记录了整个应用的当中所有需要更新的 dom节点/组件对应的 fiber 对象 ,以最小程度更新应用
      }

      // 如果有兄弟节点则返回兄弟节点,跳出这个循环,继续 beginWork
      if (siblingFiber !== null) {
        // If there is more work to do in this returnFiber, do that next.
        return siblingFiber;
        // 如果没有兄弟节点,循环找父节点的兄弟节点
      } else if (returnFiber !== null) {
        // If there's no more work in this returnFiber. Complete the returnFiber.
        workInProgress = returnFiber;
        continue;
      } else {
        // 如果都没有,则到达 rootFiber 顶点了,更新阶段完成,进入 commitRoot 提交阶段
        // We've reached the root.
        return null;
      }
    } else {
      // Incomplete 抛出过错误的情况, workInProgress 为报错的那个组件, 并不一定是能处理错误的那个组件,可能不是 classComponent 或者 没有错误处理的能力
      // 可以处理错误的组件才会标记 shouldCapture,可能是父组件
      // 即 unwindWork 执行完后 next 可能为 null 的情况
      if (workInProgress.mode & ProfileMode) {
        // Record the render duration for the fiber that errored.
        stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
      }

      // This fiber did not complete because something threw. Pop values off
      // the stack without entering the complete phase. If this is a boundary,
      // capture values if possible.
      // workInProgress 的 effect 如果为 shouldCapture ,那么返回 workInProgress ,并且将 shouldCapture 改成 DidCapture
      // 否则返回 null
      const next = unwindWork(workInProgress, nextRenderExpirationTime);
      // Because this fiber did not complete, don't reset its expiration time.
      if (workInProgress.effectTag & DidCapture) {
        // Restarting an error boundary
        stopFailedWorkTimer(workInProgress);
      } else {
        stopWorkTimer(workInProgress);
      }
      //  next 不为 null 即 workInProgress 是有 DidCapture 的 effectTag
      if (next !== null) {
        stopWorkTimer(workInProgress);

        if (enableProfilerTimer) {
          // Include the time spent working on failed children before continuing.
          if (next.mode & ProfileMode) {
            let actualDuration = next.actualDuration;
            let child = next.child;
            while (child !== null) {
              actualDuration += child.actualDuration;
              child = child.sibling;
            }
            next.actualDuration = actualDuration;
          }
        }

        // If completing this work spawned new work, do that next. We'll come
        // back here again.
        // Since we're restarting, remove anything that is not a host effect
        // from the effect tag.
        // 将 effectTag 变成只有 HostEffectMask 上所具有的 effect
        // shouldCapture、Incomplete 会被去掉
        next.effectTag &= HostEffectMask;
        // 返回的是能处理错误的那个节点,再继续 beginWork,此时会根据是否有 DidCapture 在调和子组件中做不同操作。
        return next;
      }

      if (returnFiber !== null) {
        // Mark the parent fiber as incomplete and clear its effect list.
        // 清空父链的 effect 链
        returnFiber.firstEffect = returnFiber.lastEffect = null;
         // 报错组件没有错误处理能力时,需要在父组件上增加 Incomplete,
        // 即子树中的组件如果报错,那对于父链上所有组件的 completeUnitOfWork 都会执行 unwindWork
        returnFiber.effectTag |= Incomplete;
      }

      if (siblingFiber !== null) {
        // If there is more work to do in this returnFiber, do that next.
        return siblingFiber;
      } else if (returnFiber !== null) {
        // If there's no more work in this returnFiber. Complete the returnFiber.
        workInProgress = returnFiber;
        continue;
      } else {
        return null;
      }
    }
  }

  return null;
}

// You can change the rest (and add more). export const Placement = / / 0b00000000010; export const Update = / / 0b00000000100; export const PlacementAndUpdate = / / 0b00000000110; export const Deletion = / / 0b00000001000; export const ContentReset = / / 0b00000010000; export const Callback = / / 0b00000100000; export const DidCapture = / / 0b00001000000; export const Ref = / / 0b00010000000; export const Snapshot = / / 0b00100000000;

// Update & Callback & Ref & Snapshot export const LifecycleEffectMask = / / 0b00110100100;

// Union of all host effects // HostEffectMask 为以上 effect 的并集,Incomplete、ShouldCapture 为 HostEffectMask 的补集 export const HostEffectMask = / / 0b00111111111;

export const Incomplete = / / 0b01000000000; export const ShouldCapture = / / 0b10000000000;

## 节点没有报错的情况

### completeWork 完成节点更新
- `pop` 出各种 `context` 相关的内容
- 对于 `HostComponent` 执行初始化
- 初始化监听事件
- 仅对 `HostComponent、HostText、SuspenseComponent` 有操作

---
### HostComponent 的更新

普通 `dom` 节点,如 `div、span`

#### 核心功能
- 如果不是首次渲染,会通过 `diffProperties` 计算需要更新的内容,以此进行对比来判断一个节点是否真的需要更新
- 对比的是 `dom property`,不同的 `property` 处理方式不同

#### 首次渲染
-  首次渲染,会通过 `createInstance` 创建 `dom` ,并在 `dom` 对象上记录对应的 `fiber` 节点 和 `props`
- 通过 `appendAllChildren` 构建 `dom` 树,从底往上只 `append` 一层 `child` 及 `child.sibling`
- `finalizeInitialChildren` 初始化 `dom` 节点属性、初始化事件体系
- 将 `dom` 节点记录在 `workInProgress.stateNode` 属性上, 在 `appendAllChildren` 中读取

```js
function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  // ...
  case HostComponent: {
    popHostContext(workInProgress);
    const rootContainerInstance = getRootHostContainer();
    const type = workInProgress.type;
    // current !== null => 非首次渲染
    // workInProgress.stateNode != null => 节点被创建过(挂载)
    if (current !== null && workInProgress.stateNode != null) {
      updateHostComponent(
        current,
        workInProgress,
        type,
        newProps,
        rootContainerInstance,
      );

      if (current.ref !== workInProgress.ref) {
        markRef(workInProgress);
      }
    } else {
      // 首次渲染
      if (!newProps) {
        invariant(
          workInProgress.stateNode !== null,
          'We must have new props for new mounts. This error is likely ' +
            'caused by a bug in React. Please file an issue.',
        );
        // This can happen when we abort work.
        break;
      }

      const currentHostContext = getHostContext();
      // 服务端渲染相关
      let wasHydrated = popHydrationState(workInProgress);
      if (wasHydrated) {

      } else {
        // 创建 dom instance
        let instance = createInstance(
          type,
          newProps,
          rootContainerInstance,
          currentHostContext,
          workInProgress,
        );

        // append 子节点
        appendAllChildren(instance, workInProgress, false, false);

        // Certain renderers require commit-time effects for initial mount.
        // (eg DOM renderer supports auto-focus for certain elements).
        // Make sure such renderers get scheduled for later work.
        if (
          // 初始化 react 事件体系,初始化 DOM 节点属性
          finalizeInitialChildren(
            instance,
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
          )
        ) {
          // 节点需要 autofocus 时才mark update
          markUpdate(workInProgress);
        }
        // 将 dom节点 记录在 stateNode 属性上, 在 appendAllChildren 中读取
        workInProgress.stateNode = instance;
      }

      if (workInProgress.ref !== null) {
        // If there is a ref on a host node we need to schedule a callback
        markRef(workInProgress);
      }
    }
    break;
  }
}

createInstance 创建 DOM

function createInstance(
  type: string,
  props: Props, // newProps
  rootContainerInstance: Container, // 
  hostContext: HostContext,
  internalInstanceHandle: Object, // workInProgress 
): Instance {
  let parentNamespace: string;
  if (__DEV__) {
  } else {
    parentNamespace = ((hostContext: any): HostContextProd);
  }
  // 创建 dom Element 
  const domElement: Instance = createElement(
    type,
    props,
    rootContainerInstance,
    parentNamespace,
  );
  // cache Fiber 将 fiber 对象存储在 domElemeent 的 internalInstanceKey 属性上
  precacheFiberNode(internalInstanceHandle, domElement);
  // cache props 将 newProps 存储在 domElement 的 internalEventHandlersKey 属性上,props 会有 dom 的 attr 相关的属性
  updateFiberProps(domElement, props);
  return domElement;
}

// createElement 创建 dom

function createElement(
  type: string,
  props: Object,
  rootContainerElement: Element | Document,
  parentNamespace: string,
): Element {
  let isCustomComponentTag;

  // Document 对象
  const ownerDocument: Document = getOwnerDocumentFromRootContainer(
    rootContainerElement,
  );
  let domElement: Element;
  let namespaceURI = parentNamespace;
  if (namespaceURI === HTML_NAMESPACE) {
    namespaceURI = getIntrinsicNamespace(type);
  }
  if (namespaceURI === HTML_NAMESPACE) {
    if (__DEV__) {}
    // 特殊节点处理,如果直接创建 script 标签,会自动执行里面的脚本
    // 通过先插入,后删除的方式获取 script 标签对应的 dom
    if (type === 'script') {
      // Create the script via .innerHTML so its "parser-inserted" flag is
      // set to true and it does not execute
      const div = ownerDocument.createElement('div');
      div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
      // This is guaranteed to yield a script element.
      const firstChild = ((div.firstChild: any): HTMLScriptElement);
      domElement = div.removeChild(firstChild);
    } else if (typeof props.is === 'string') {
      domElement = ownerDocument.createElement(type, {is: props.is});
    } else {
      // 原生 dom 创建元素
      domElement = ownerDocument.createElement(type);
      // select 标签设置属性
      if (type === 'select' && props.multiple) {
        const node = ((domElement: any): HTMLSelectElement);
        node.multiple = true;
      }
    }
  } else {
    // 不是 html 的 nameSpace 
    domElement = ownerDocument.createElementNS(namespaceURI, type);
  }
  // 返回 domElement 
  return domElement;
}

appendAllChildren 构建 dom 树

appendAllChildren = function(
  parent: Instance, // 创建的 dom 
  workInProgress: Fiber,
  needsVisibilityToggle: boolean,
  isHidden: boolean,
) {
  // We only have the top Fiber that was created but we need recurse down its
  // children to find all the terminal nodes.
  // 当前节点的子节点
  let node = workInProgress.child;
  while (node !== null) {
    if (node.tag === HostComponent || node.tag === HostText) {
      // 子节点是 原生 dom 节点 或者纯文本节点
      // 直接调用原生 dom 的 appendChild
      appendInitialChild(parent, node.stateNode);
    } else if (node.tag === HostPortal) {
      // If we have a portal child, 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) {
      // 子节点不是 dom 标签或者纯文本节点, 且 child.child 不为null
      node.child.return = node;
      // 重新赋值 node 为 node.child,并跳过此次循环,进入下一次循环,一直往下找直到找到 dom 节点 或者 纯文本节点
      node = node.child;
      continue;
    }

    if (node === workInProgress) {
      return;
    }
    // 这个 while 循环目的是找到有兄弟节点的父节点,如果父节点有兄弟节点,则跳出这个小循环,会去遍历其兄弟节点
    // 如果没有兄弟节点,继续往上找最终会回到 node.return === workInProgress 跳出循环
    while (node.sibling === null) {
      if (node.return === null || node.return === workInProgress) {
        return;
      }
      node = node.return;
    }
    // 将兄弟节点的父节点都指向同一个
    node.sibling.return = node.return;
    // 有兄弟节点继续遍历
    node = node.sibling;
  }
};

finalizeInitialChildren 初始化 DOM 节点属性,初始化事件体系

function finalizeInitialChildren(
  domElement: Instance,
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
): boolean {
  setInitialProperties(domElement, type, props, rootContainerInstance);
  // 节点需要 autofocus 时才 mark update
  return shouldAutoFocusHostComponent(type, props);
}

// setInitialProperties

function setInitialProperties(
  domElement: Element,
  tag: string,
  rawProps: Object,
  rootContainerElement: Element | Document,
): void {
  const isCustomComponentTag = isCustomComponent(tag, rawProps);
  if (__DEV__) {}

  // TODO: Make sure that we check isMounted before firing any of these events.
  // trapBubbledEvent 绑定不同事件
  let props: Object;
  switch (tag) {
    case 'iframe':
    case 'object':
      trapBubbledEvent(TOP_LOAD, domElement);
      props = rawProps;
      break;
    case 'video':
    case 'audio':
      // Create listener for each media event
      for (let i = 0; i < mediaEventTypes.length; i++) {
        trapBubbledEvent(mediaEventTypes[i], domElement);
      }
      props = rawProps;
      break;
    case 'source':
      trapBubbledEvent(TOP_ERROR, domElement);
      props = rawProps;
      break;
    case 'img':
    case 'image':
    case 'link':
      trapBubbledEvent(TOP_ERROR, domElement);
      trapBubbledEvent(TOP_LOAD, domElement);
      props = rawProps;
      break;
    case 'form':
      trapBubbledEvent(TOP_RESET, domElement);
      trapBubbledEvent(TOP_SUBMIT, domElement);
      props = rawProps;
      break;
    case 'details':
      trapBubbledEvent(TOP_TOGGLE, domElement);
      props = rawProps;
      break;
      // input option select textarea 为可交互输入的组件
    case 'input':
      ReactDOMInput.initWrapperState(domElement, rawProps);
      props = ReactDOMInput.getHostProps(domElement, rawProps);
      trapBubbledEvent(TOP_INVALID, domElement);
      // For controlled components we always need to ensure we're listening
      // to onChange. Even if there is no listener.
      ensureListeningTo(rootContainerElement, 'onChange');
      break;
    case 'option':
      ReactDOMOption.validateProps(domElement, rawProps);
      props = ReactDOMOption.getHostProps(domElement, rawProps);
      break;
    case 'select':
      ReactDOMSelect.initWrapperState(domElement, rawProps);
      props = ReactDOMSelect.getHostProps(domElement, rawProps);
      trapBubbledEvent(TOP_INVALID, domElement);
      // For controlled components we always need to ensure we're listening
      // to onChange. Even if there is no listener.
      ensureListeningTo(rootContainerElement, 'onChange');
      break;
    case 'textarea':
      ReactDOMTextarea.initWrapperState(domElement, rawProps);
      props = ReactDOMTextarea.getHostProps(domElement, rawProps);
      trapBubbledEvent(TOP_INVALID, domElement);
      // For controlled components we always need to ensure we're listening
      // to onChange. Even if there is no listener.
      ensureListeningTo(rootContainerElement, 'onChange');
      break;
    default:
      props = rawProps;
  }

  // 常用 props 错误提醒
  assertValidProps(tag, props);

  // 初始化 DOM 节点属性
  setInitialDOMProperties(
    tag,
    domElement,
    rootContainerElement,
    props,
    isCustomComponentTag,
  );
  // ...
}

// setInitialDOMProperties 
function setInitialDOMProperties(
  tag: string,
  domElement: Element,
  rootContainerElement: Element | Document,
  nextProps: Object,
  isCustomComponentTag: boolean,
): void {
  for (const propKey in nextProps) {
    if (!nextProps.hasOwnProperty(propKey)) {
      continue;
    }
    const nextProp = nextProps[propKey];
    if (propKey === STYLE) {
    // style 属性
      if (__DEV__) {
        if (nextProp) {
          // Freeze the next style object so that we can assume it won't be
          // mutated. We have already warned for this in the past.
          Object.freeze(nextProp);
        }
      }
      // Relies on `updateStylesByID` not mutating `styleUpdates`.
      // 设置样式,对于 width 类属性,自动加 px
      CSSPropertyOperations.setValueForStyles(domElement, nextProp);
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      // 文本属性,直接用原生 node.innerHTML = html 设置
      const nextHtml = nextProp ? nextProp[HTML] : undefined;
      if (nextHtml != null) {
        setInnerHTML(domElement, nextHtml);
      }
    } else if (propKey === CHILDREN) {
      // 子节点,这里只会是文本或数字了,dom 节点或者组件已经在 appendAllChildren 操作完了
      if (typeof nextProp === 'string') {
        // Avoid setting initial textContent when the text is empty. In IE11 setting
        // textContent on a <textarea> will cause the placeholder to not
        // show within the <textarea> until it has been focused and blurred again.
        // https://github.com/facebook/react/issues/6731#issuecomment-254874553
        const canSetTextContent = tag !== 'textarea' || nextProp !== '';
        if (canSetTextContent) {
          setTextContent(domElement, nextProp);
        }
      } else if (typeof nextProp === 'number') {
        setTextContent(domElement, '' + nextProp);
      }
    } else if (
      propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
      propKey === SUPPRESS_HYDRATION_WARNING
    ) {
      // Noop
    } else if (propKey === AUTOFOCUS) {
      // We polyfill it separately on the client during commit.
      // We could have excluded it in the property list instead of
      // adding a special case here, but then it wouldn't be emitted
      // on server rendering (but we *do* want to emit it in SSR).
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      // 事件相关
      if (nextProp != null) {
        if (__DEV__ && typeof nextProp !== 'function') {
          warnForInvalidEventListener(propKey, nextProp);
        }
        // 事件监听
        ensureListeningTo(rootContainerElement, propKey);
      }
    } else if (nextProp != null) {
      // 最终 node.setAttribute 设置属性,或者 removeAttribute 删除不必要的属性
      DOMPropertyOperations.setValueForProperty(
        domElement,
        propKey,
        nextProp,
        isCustomComponentTag,
      );
    }
  }
}

updateHostComponent 更新渲染

updateHostComponent = function(
    current: Fiber,
    workInProgress: Fiber,
    type: Type,
    newProps: Props,
    rootContainerInstance: Container,
  ) {
    // If we have an alternate, that means this is an update and we need to
    // schedule a side-effect to do the updates.
    const oldProps = current.memoizedProps;
    // 新老 props 相同,直接 return 
    if (oldProps === newProps) {
      return;
    }
    // 当前节点对应的 DOM 节点
    const instance: Instance = workInProgress.stateNode;
    const currentHostContext = getHostContext();
    // TODO: Experiencing an error where oldProps is null. Suggests a host
    // component is hitting the resume path. Figure out why. Possibly
    // related to `hidden`.
    // updatePayload 为调用 diffProperties 返回的结果
    const updatePayload = prepareUpdate(
      instance,
      type,
      oldProps,
      newProps,
      rootContainerInstance,
      currentHostContext,
    );
    // TODO: Type this specific to this type of component.
    workInProgress.updateQueue = (updatePayload: any);
    // If the update payload indicates that there is a change or if there
    // is a new ref we mark this as an update. All the work is done in commitWork.
    // 如果有 updatePayload 则要标记更新,对于 input,option, select, textarea 默认为空数组,即便是 props 没有更新的内容,仍旧需要标记 update 
    if (updatePayload) {
      markUpdate(workInProgress);
    }
  };

diffProperties

function diffProperties(
  domElement: Element,
  tag: string,
  lastRawProps: Object,
  nextRawProps: Object,
  rootContainerElement: Element | Document,
): null | Array<mixed> {
  if (__DEV__) {
    validatePropertiesInDevelopment(tag, nextRawProps);
  }

  let updatePayload: null | Array<any> = null;

  let lastProps: Object;
  let nextProps: Object;
  // 特殊标签
  switch (tag) {
    case 'input':
      lastProps = ReactDOMInput.getHostProps(domElement, lastRawProps);
      nextProps = ReactDOMInput.getHostProps(domElement, nextRawProps);
      updatePayload = [];
      break;
    case 'option':
      lastProps = ReactDOMOption.getHostProps(domElement, lastRawProps);
      nextProps = ReactDOMOption.getHostProps(domElement, nextRawProps);
      updatePayload = [];
      break;
    case 'select':
      lastProps = ReactDOMSelect.getHostProps(domElement, lastRawProps);
      nextProps = ReactDOMSelect.getHostProps(domElement, nextRawProps);
      updatePayload = [];
      break;
    case 'textarea':
      lastProps = ReactDOMTextarea.getHostProps(domElement, lastRawProps);
      nextProps = ReactDOMTextarea.getHostProps(domElement, nextRawProps);
      updatePayload = [];
      break;
    default:
      lastProps = lastRawProps;
      nextProps = nextRawProps;
      if (
        typeof lastProps.onClick !== 'function' &&
        typeof nextProps.onClick === 'function'
      ) {
        // TODO: This cast may not be sound for SVG, MathML or custom elements.
        trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
      }
      break;
  }

  // props 错误提醒
  assertValidProps(tag, nextProps);

  let propKey;
  let styleName;
  let styleUpdates = null;
  // 循环老的 props,如果新 props 没有而老 props 有则要删除(style属性:将value置为 ’‘,其他属性置为 null)这个属性
  for (propKey in lastProps) {
    if (
      // 新的有
      nextProps.hasOwnProperty(propKey) ||
      // 老的没有
      !lastProps.hasOwnProperty(propKey) ||
      // 老的为 null
      lastProps[propKey] == null
    ) {
      // 如果符合这个条件则跳过,否则是新的没有且老的有,那么就要删除
      continue;
    }
    // 不符合以上条件则是删除操作
    if (propKey === STYLE) {
      const lastStyle = lastProps[propKey];
      for (styleName in lastStyle) {
        if (lastStyle.hasOwnProperty(styleName)) {
          if (!styleUpdates) {
            styleUpdates = {};
          }
          // 设置删除老的 style props
          styleUpdates[styleName] = '';
        }
      }
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
      // Noop. This is handled by the clear text mechanism.
    } else if (
      propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
      propKey === SUPPRESS_HYDRATION_WARNING
    ) {
      // Noop
    } else if (propKey === AUTOFOCUS) {
      // Noop. It doesn't work on updates anyway.
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      // This is a special case. If any listener updates we need to ensure
      // that the "current" fiber pointer gets updated so we need a commit
      // to update this element.
      if (!updatePayload) {
        updatePayload = [];
      }
    } else {
      // For all other deleted properties we add it to the queue. We use
      // the whitelist in the commit phase instead.
      // 要删除的属性,则 value 都设置为 null
      (updatePayload = updatePayload || []).push(propKey, null);
    }
  }

  // 循环新的 props,如果新 props 有这个 key,且新老 props[key] 值不想等则将这个 key 标记更新( style属性则为直接设置 style[key] = newValue ),
  for (propKey in nextProps) {
    // newValue
    const nextProp = nextProps[propKey];
    // oldValue
    const lastProp = lastProps != null ? lastProps[propKey] : undefined;
    if (
      // 自身没有这个 key
      !nextProps.hasOwnProperty(propKey) ||
      // 新老相等
      nextProp === lastProp ||
      // 均为 null
      (nextProp == null && lastProp == null)
    ) {
      // 符合以上条件则跳过, 否则就要加入 updatePayload 中
      continue;
    }
    // 对 style 属性的操作
    if (propKey === STYLE) {
      if (__DEV__) {
        if (nextProp) {
          // Freeze the next style object so that we can assume it won't be
          // mutated. We have already warned for this in the past.
          Object.freeze(nextProp);
        }
      }
      if (lastProp) {
        // Unset styles on `lastProp` but not on `nextProp`.
        for (styleName in lastProp) {
          if (
            // style 在老的 props 中,但不在新的 props 中,则属性对应的 value 设置为 ‘’(对应删除)
            lastProp.hasOwnProperty(styleName) &&
            (!nextProp || !nextProp.hasOwnProperty(styleName))
          ) {
            if (!styleUpdates) {
              styleUpdates = {};
            }
            styleUpdates[styleName] = '';
          }
        }
        // Update styles that changed since `lastProp`.
        for (styleName in nextProp) {
          // 如果新props 有且新老prop 值不想等,赋值 styleUpdates[styleName] 为新的值
          if (
            nextProp.hasOwnProperty(styleName) &&
            lastProp[styleName] !== nextProp[styleName]
          ) {
            if (!styleUpdates) {
              styleUpdates = {};
            }
            styleUpdates[styleName] = nextProp[styleName];
          }
        }
      } else {
        // Relies on `updateStylesByID` not mutating `styleUpdates`.
        if (!styleUpdates) {
          if (!updatePayload) {
            updatePayload = [];
          }
          // 没有老 props 直接添加
          updatePayload.push(propKey, styleUpdates);
        }
        styleUpdates = nextProp;
      }
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      const nextHtml = nextProp ? nextProp[HTML] : undefined;
      const lastHtml = lastProp ? lastProp[HTML] : undefined;
      if (nextHtml != null) {
        if (lastHtml !== nextHtml) {
          (updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
        }
      } else {
        // TODO: It might be too late to clear this if we have children
        // inserted already.
      }
    } else if (propKey === CHILDREN) {
      // 子节点,文本类型的,不相同直接 push 
      if (
        lastProp !== nextProp &&
        (typeof nextProp === 'string' || typeof nextProp === 'number')
      ) {
        (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
      }
    } else if (
      propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
      propKey === SUPPRESS_HYDRATION_WARNING
    ) {
      // Noop
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      if (nextProp != null) {
        // We eagerly listen to this even though we haven't committed yet.
        if (__DEV__ && typeof nextProp !== 'function') {
          warnForInvalidEventListener(propKey, nextProp);
        }
        // 事件相关
        ensureListeningTo(rootContainerElement, propKey);
      }
      if (!updatePayload && lastProp !== nextProp) {
        // This is a special case. If any listener updates we need to ensure
        // that the "current" props pointer gets updated so we need a commit
        // to update this element.
        updatePayload = [];
      }
    } else {
      // For any other property we always add it to the queue and then we
      // filter it out using the whitelist during the commit.
      (updatePayload = updatePayload || []).push(propKey, nextProp);
    }
  }
  if (styleUpdates) {
    (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
  }
  // updatePayload 的格式为 [k1, v1, k2, v2, k3, v3]
  return updatePayload;
}

HostText 更新

case HostText: {
  let newText = newProps;
  // 更新渲染
  if (current && workInProgress.stateNode != null) {
    const oldText = current.memoizedProps;
    // If we have an alternate, that means this is an update and we need
    // to schedule a side-effect to do the updates.
    // 新老text不同则标记更新
    updateHostText(current, workInProgress, oldText, newText);
  } else {
    // 首次渲染 
    if (typeof newText !== 'string') {
      invariant(
        workInProgress.stateNode !== null,
        'We must have new props for new mounts. This error is likely ' +
          'caused by a bug in React. Please file an issue.',
      );
      // This can happen when we abort work.
    }
    const rootContainerInstance = getRootHostContainer();
    const currentHostContext = getHostContext();
    let wasHydrated = popHydrationState(workInProgress);
    if (wasHydrated) {
      if (prepareToHydrateHostTextInstance(workInProgress)) {
        markUpdate(workInProgress);
      }
    } else {
      // 创建 textInstance 
      workInProgress.stateNode = createTextInstance(
        newText,
        rootContainerInstance,
        currentHostContext,
        workInProgress,
      );
    }
  }
  break;
}

// 判断不相同,则标记 update 
updateHostText = function(
    current: Fiber,
    workInProgress: Fiber,
    oldText: string,
    newText: string,
  ) {
    // If the text differs, mark it as an update. All the work in done in commitWork.
    if (oldText !== newText) {
      markUpdate(workInProgress);
    }
  };

// createTextInstance 创建 textInstance
function createTextInstance(
  text: string,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
): TextInstance {
  if (__DEV__) {
    const hostContextDev = ((hostContext: any): HostContextDev);
    validateDOMNesting(null, text, hostContextDev.ancestorInfo);
  }
  // 调用 document.createTextNode
  const textNode: TextInstance = createTextNode(text, rootContainerInstance);
  // cache Fiber, 将 fiber 对象存储在 domElemeent 的 internalInstanceKey 属性上
  precacheFiberNode(internalInstanceHandle, textNode);
  return textNode;
}

resetChildExpirationTime 重置 resetChildExpirationTime

function resetChildExpirationTime(
  workInProgress: Fiber,
  renderTime: ExpirationTime,
) {
  // renderTime !== Never => 当前任务执行更新
  // workInProgress.childExpirationTime === Never => 当前节点的子节点没有更新
  // 直接返回
  if (renderTime !== Never && workInProgress.childExpirationTime === Never) {
    // The children of this component are hidden. Don't bubble their
    // expiration times.
    return;
  }

  let newChildExpirationTime = NoWork;

  // Bubble up the earliest expiration time.
  if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
    // ....
  } else {
    let child = workInProgress.child;
    while (child !== null) {
      // 子节点的 expirationTime
      const childUpdateExpirationTime = child.expirationTime;
      // 子节点的子树中更新优先级最高的那个 expirationTime
      const childChildExpirationTime = child.childExpirationTime;
      if (
        newChildExpirationTime === NoWork ||
        (childUpdateExpirationTime !== NoWork && // 找到不为 NoWork且优先级最高的子节点对应的 expirationTime
          childUpdateExpirationTime < newChildExpirationTime)
      ) {
        newChildExpirationTime = childUpdateExpirationTime;
      }
      if (
        newChildExpirationTime === NoWork ||
        (childChildExpirationTime !== NoWork && // 找到子节点子树中优先级不为 NoWork 且更新优先级最高的 expirationTime
          childChildExpirationTime < newChildExpirationTime)
      ) {
        newChildExpirationTime = childChildExpirationTime;
      }
      // 横向查找(有兄弟节点的情况才会要去找更新优先级最高的 expirationTime)
      child = child.sibling;
    }
  }
  // 更新当前节点的 childExpirationTime
  workInProgress.childExpirationTime = newChildExpirationTime;
}

节点有报错的情况

// renderRoot

do {
  try {
    workLoop(isYieldy);
  } catch (thrownValue) {
    // 错误处理核心点:
    // 给报错的节点增加 Incomplete 副作用,在 completeUnitOfWork 时会判断存在 Incomplete 则调用 unwindWork, 不存在则调用 completeWork
    // 给父链上具有 error boundary 的节点增加副作用
    // 创建错误相关的更新

    // 非正常流程,不应该为 null
    if (nextUnitOfWork === null) {
      // This is a fatal error.
      // 致命错误,直接中断渲染流程
      didFatal = true;
      // 设置 nextFlushedRoot.expirationTime = NoWork; 
      onUncaughtError(thrownValue);
    } else {
      if (__DEV__) {
        // Reset global debug state
        // We assume this is defined in DEV
        (resetCurrentlyProcessingQueue: any)();
      }

      const failedUnitOfWork: Fiber = nextUnitOfWork;
      if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
        replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
      }

      // TODO: we already know this isn't true in some cases.
      // At least this shows a nicer error message until we figure out the cause.
      // https://github.com/facebook/react/issues/12449#issuecomment-386727431
      invariant(
        nextUnitOfWork !== null,
        'Failed to replay rendering after an error. This ' +
          'is likely caused by a bug in React. Please file an issue ' +
          'with a reproducing case to help us find it.',
      );

      const sourceFiber: Fiber = nextUnitOfWork;
      let returnFiber = sourceFiber.return;
      // 在更新 rootFiber 时出现致命错误
      if (returnFiber === null) {
        // This is the root. The root could capture its own errors. However,
        // we don't know if it errors before or after we pushed the host
        // context. This information is needed to avoid a stack mismatch.
        // Because we're not sure, treat this as a fatal error. We could track
        // which phase it fails in, but doesn't seem worth it. At least
        // for now.
        didFatal = true;
        onUncaughtError(thrownValue);
      } else {
        // 常规错误处理, 给报错节点增加 Incomplete 的 effect
        throwException(
          root,
          returnFiber,
          sourceFiber,
          thrownValue,
          nextRenderExpirationTime,
        );
        // 节点报错,则不会继续渲染它的子节点,直接 completeUnitOfWork ,直接走 unwindWork 流程
        nextUnitOfWork = completeUnitOfWork(sourceFiber);
        continue;
      }
    }
  }
  break;
} while (true);

onUncaughtError

onUncaughtError(error: mixed) {
  nextFlushedRoot.expirationTime = NoWork;
  if (!hasUnhandledError) {
    hasUnhandledError = true;
    unhandledError = error;
  }
}

throwException

function throwException(
  root: FiberRoot,
  returnFiber: Fiber,
  sourceFiber: Fiber,
  value: mixed, // 抛出来的错误 value
  renderExpirationTime: ExpirationTime,
) {
  // The source fiber did not complete.
  sourceFiber.effectTag |= Incomplete;
  // Its effect list is no longer valid.
  // 清空 effect 链
  sourceFiber.firstEffect = sourceFiber.lastEffect = null;

  // throw 的是 promise 的情况,suspense 组件
  if (
    value !== null &&
    typeof value === 'object' &&
    typeof value.then === 'function'
  ) { // ...}

  // We didn't find a boundary that could handle this type of exception. Start
  // over and traverse parent path again, this time treating the exception
  // as an error.
  // nextRenderDidError = true 标记错误变量
  renderDidError();
  // 处理过的错误信息, 类似 Error 对象,有错误产生的文件、方法、行数等信息
  value = createCapturedValue(value, sourceFiber);
  let workInProgress = returnFiber;
  // 如果组件没有 getDerivedStateFromError 或 componentDidCatch,则是没有错误处理功能的
  // 需要往上找找到第一个可以处理错误的 classComponent 来进行错误的 update 的创建,并加入到 updateQueue 中,在 commit 阶段进行调用
  // 如果找不到可以进行错误处理的 classComponent,那么会在 hostRoot 上进行处理,算是一个内置的错误处理的方式,也会创建对应的错误的 update 
  do {
    switch (workInProgress.tag) {
      case HostRoot: {
        const errorInfo = value;
        // 标记 effect 
        workInProgress.effectTag |= ShouldCapture;
        workInProgress.expirationTime = renderExpirationTime;
        // 类似 setState 的创建一个错误处理的 update,创建 firstCapturedUpdate、lastCapturedUpdate链 , 创建 root 节点的错误更新
        const update = createRootErrorUpdate(
          workInProgress,
          errorInfo,
          renderExpirationTime,
        );
        // 类似 enqueueUpdate 的推入 updateQueue
        enqueueCapturedUpdate(workInProgress, update);
        return;
      }
      case ClassComponent:
        // Capture and retry
        const errorInfo = value;
        const ctor = workInProgress.type;
        const instance = workInProgress.stateNode;

        if (
          // 没有 DidCapture 
          (workInProgress.effectTag & DidCapture) === NoEffect &&
          // 并且有 静态方法 getDerivedStateFromError 或者 实例的 componentDidCatch 错误处理函数
          (typeof ctor.getDerivedStateFromError === 'function' ||
            (instance !== null &&
              typeof instance.componentDidCatch === 'function' &&
              !isAlreadyFailedLegacyErrorBoundary(instance)))
        ) {
          // 标记 effect 为 ShouldCapture
          workInProgress.effectTag |= ShouldCapture;
          workInProgress.expirationTime = renderExpirationTime;
          // Schedule the error boundary to re-render using updated state
          // 类似 setState 的创建一个错误处理的 update, 创建 firstCapturedUpdate、lastCapturedUpdate链 
          const update = createClassErrorUpdate(
            workInProgress,
            errorInfo,
            renderExpirationTime,
          );
          // 加入到 updateQueue 中
          enqueueCapturedUpdate(workInProgress, update);
          return;
        }
        break;
      default:
        break;
    }
    // 往上遍历
    workInProgress = workInProgress.return;
  } while (workInProgress !== null);
}
function createRootErrorUpdate(
  fiber: Fiber,
  errorInfo: CapturedValue<mixed>,
  expirationTime: ExpirationTime,
): Update<mixed> {
  const update = createUpdate(expirationTime);
  // Unmount the root by rendering null.
  // 对比 setState 的 tag 为 UpdateState, 这里为捕获错误更新
  update.tag = CaptureUpdate;
  // Caution: React DevTools currently depends on this property
  // being called "element".
  // rootFiber 的 payload 为 element 属性的对象,这里置为 null,不渲染任何子节点
  update.payload = {element: null};
  const error = errorInfo.value;
  update.callback = () => {
    onUncaughtError(error);
    logError(fiber, errorInfo);
  };
  return update;
}
function createClassErrorUpdate(
  fiber: Fiber,
  errorInfo: CapturedValue<mixed>,
  expirationTime: ExpirationTime,
): Update<mixed> {
  // 创建 update
  const update = createUpdate(expirationTime);
  // 标记为捕获错误更新
  update.tag = CaptureUpdate;
  const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
  // 如果有 getDerivedStateFromError 则将其返回结果设置为 payload 回调更新为 state
  if (typeof getDerivedStateFromError === 'function') {
    const error = errorInfo.value;
    update.payload = () => {
      return getDerivedStateFromError(error);
    };
  }

  const inst = fiber.stateNode;
  // 如果有 componentDidCatch 则加入到 callback 中,等到组件更新完后才调用
  if (inst !== null && typeof inst.componentDidCatch === 'function') {
    update.callback = function callback() {
      const error = errorInfo.value;
      const stack = errorInfo.stack;
      logError(fiber, errorInfo);
      this.componentDidCatch(error, {
        componentStack: stack !== null ? stack : '',
      });
    };
  }
  return update;
}

unwindWork

function unwindWork(
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  switch (workInProgress.tag) {
    case ClassComponent: {
      const Component = workInProgress.type;
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      const effectTag = workInProgress.effectTag;
      // 如果有 shouldCapture 
      if (effectTag & ShouldCapture) {
        // 去掉 shouldCapture 增加 DidCapture
        workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
        return workInProgress;
      }
      return null;
    }
    case HostRoot: {
      popHostContainer(workInProgress);
      popTopLevelLegacyContextObject(workInProgress);
      const effectTag = workInProgress.effectTag;
      invariant(
        (effectTag & DidCapture) === NoEffect,
        'The root failed to unmount after an error. This is likely a bug in ' +
          'React. Please file an issue.',
      );
      workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
      return workInProgress;
    }
    case HostComponent: {
      popHostContext(workInProgress);
      return null;
    }
    case SuspenseComponent: {
      const effectTag = workInProgress.effectTag;
      if (effectTag & ShouldCapture) {
        workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
        // Captured a suspense effect. Set the boundary's `alreadyCaptured`
        // state to true so we know to render the fallback.
        const current = workInProgress.alternate;
        const currentState: SuspenseState | null =
          current !== null ? current.memoizedState : null;
        let nextState: SuspenseState | null = workInProgress.memoizedState;
        if (nextState === null) {
          // No existing state. Create a new object.
          nextState = {
            alreadyCaptured: true,
            didTimeout: false,
            timedOutAt: NoWork,
          };
        } else if (currentState === nextState) {
          // There is an existing state but it's the same as the current tree's.
          // Clone the object.
          nextState = {
            alreadyCaptured: true,
            didTimeout: nextState.didTimeout,
            timedOutAt: nextState.timedOutAt,
          };
        } else {
          // Already have a clone, so it's safe to mutate.
          nextState.alreadyCaptured = true;
        }
        workInProgress.memoizedState = nextState;
        // Re-render the boundary.
        return workInProgress;
      }
      return null;
    }
    case HostPortal:
      popHostContainer(workInProgress);
      return null;
    case ContextProvider:
      popProvider(workInProgress);
      return null;
    default:
      return null;
  }
}