React-Commit阶段解析

11/24/2023 React

React 的 commit 阶段是整个渲染流程中的最后一个阶段,它负责将所有的变更应用到真实的 DOM 中,同时执行一些生命周期函数、Hooks 的回调,以及处理事件系统的绑定。在这个阶段,React 执行了一系列的任务来确保更新的正确应用。

# 主要任务

  1. 执行生命周期函数和 Hooks 的回调: 这包括 FunctionComponent、ForwardRef、SimpleMemoComponent 等的 useLayoutEffectuseEffect 回调,以及 ClassComponent 的 componentDidMountcomponentDidUpdate 生命周期。
  2. 提交 Fiber 的修改到浏览器 DOM 中: 通过 commitReconciliationEffects 函数将 Fiber 树上的修改同步到浏览器的 DOM 中。
  3. 事件系统的绑定: 为 HostComponent 注册事件,将整个 props 记录下来,等待事件分发的时候执行回调。
  4. 再次提交渲染: 通过 ensureRootIsScheduled 函数再次调度 React 的渲染,执行下一轮的渲染。

# 具体执行流程

  1. commitMutationEffects 函数: 首先提交浏览器 DOM 的修改,包括执行 commitReconciliationEffects 但因为 javascript 还在执行,所以浏览器的渲染被阻塞住。

    recursivelyTraverseMutationEffects(root, finishedWork, lanes);
    commitReconciliationEffects(finishedWork);
    
    if (flags & Update) {
      try {
        commitHookEffectListUnmount(
          HookInsertion | HookHasEffect,
          finishedWork,
          finishedWork.return
        );
        commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
      } catch (error) {
        captureCommitPhaseError(finishedWork, finishedWork.return, error);
      }
      // Layout effects are destroyed during the mutation phase so that all
      // destroy functions for all fibers are called before any create functions.
      // This prevents sibling component effects from interfering with each other,
      // e.g. a destroy function in one component should never override a ref set
      // by a create function in another component during the same commit.
      if (shouldProfile(finishedWork)) {
        try {
          startLayoutEffectTimer();
          commitHookEffectListUnmount(
            HookLayout | HookHasEffect,
            finishedWork,
            finishedWork.return
          );
        } catch (error) {
          captureCommitPhaseError(finishedWork, finishedWork.return, error);
        }
        recordLayoutEffectDuration(finishedWork);
      } else {
        try {
          commitHookEffectListUnmount(
            HookLayout | HookHasEffect,
            finishedWork,
            finishedWork.return
          );
        } catch (error) {
          captureCommitPhaseError(finishedWork, finishedWork.return, error);
        }
      }
    }
    
  2. commitLayoutEffects 函数: 执行 useLayoutEffect 和 ClassComponent 的生命周期函数。

    switch (finishedWork.tag) {
    	case FunctionComponent:
    	case ForwardRef:
    	case SimpleMemoComponent: {
    	  recursivelyTraverseLayoutEffects(
    	    finishedRoot,
    	    finishedWork,
    	    committedLanes,
    	  );
    	  if (flags & Update) {
    	    commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
    	  }
    	  break;
    	}
    	case ClassComponent: {
    	  recursivelyTraverseLayoutEffects(
    	    finishedRoot,
    	    finishedWork,
    	    committedLanes,
    	  );
    	  if (flags & Update) {
    	    commitClassLayoutLifecycles(finishedWork, current);
    	  }
    
    	  if (flags & Callback) {
    	    commitClassCallbacks(finishedWork);
    	  }
    
    	  if (flags & Ref) {
    	    safelyAttachRef(finishedWork, finishedWork.return);
    	  }
    	  break;
    	}
    ...
    }
    
  3. flushPassiveEffects 函数: 在浏览器 DOM 渲染完毕后执行,用于执行 FunctionComponent 的 useEffect 回调。

    commitPassiveUnmountEffects(root.current);
    commitPassiveMountEffects(root, root.current, lanes, transitions);
    
  4. ensureRootIsScheduled 函数: Commit 阶段在最后的阶段再调度一次 React 的渲染,去执行下一次的渲染流程。

# 结语

React 的 commit 阶段是整个渲染流程的收尾,它确保了变更的正确应用和一些副作用的执行。深入理解这个阶段对于优化和调试 React 应用非常重要。