Skip to content

本文要弄清楚的问题

  1. commitPlacement 只处理两种类型:HostComponent 和 HostRoot,HostRoot我可以理解,最终要commitRoot嘛,但是HostComponent是什么情况?不是在completeWork就已经完成DOM的操作了吗?

前言

本文基于 HostRootHostTextHostComponentFunctionComponent 进行非并发模式下的渲染更新流程分析,主要侧重于

  • render阶段的 beginWork():进行diff比较,形成新的 fiber 数据
  • render阶段的 completeWork():进行非root的DOM操作,比如判断是否有DOM,没有则创建,有的检测属性是否变化,打上 Update 的 flags 标记
  • commit阶段的 commitMutationEffectsOnFiber():根据 flags/subtreeFlags 进行不同 flags 的处理,比如 Update进行数据更新、Placement进行DOM的插入(主要是 #root.appendChild(DOM元素)

本文不进行 ClassComponent 的分析

整体流程图(TODO)


整体渲染流程分析

根据整体流程图进行更新渲染流程的分析

HostRoot

beginWork()

与首次渲染一致,没什么特殊处理

completeWork()

与首次渲染一致,没什么特殊处理

commitMutationEffectsOnFiber()

与首次渲染一致,没什么特殊处理

HostText

beginWork()

与首次渲染一致,没什么特殊处理

completeWork()

比对 oldText 和 newText 是否不同,如果不同则打上 Update 的 flags 标记

ts
case HostText: {
    var newText = newProps;

    if (current && workInProgress.stateNode != null) {
        var oldText = current.memoizedProps;
        updateHostText$1(current, workInProgress, oldText, newText);
    } else {
        //...
    }
    bubbleProperties(workInProgress);
    return null;
}
updateHostText$1 = function (current, workInProgress, oldText, newText) {
  if (oldText !== newText) {
    markUpdate(workInProgress);
  }
};

commitMutationEffectsOnFiber()

在 commit 阶段,如果有 Update 的标记,则进行文本的更新操作

ts
case HostText: {
    recursivelyTraverseMutationEffects(root, finishedWork);
    commitReconciliationEffects(finishedWork);
    
    if (flags & Update) {
      var textInstance = finishedWork.stateNode;
      var newText = finishedWork.memoizedProps;
      var oldText = current !== null ? current.memoizedProps : newText;
      // commitTextUpdate:textInstance.nodeValue = newText;
      commitTextUpdate(textInstance, oldText, newText);
    }
    return;
}

HostComponent

beginWork()

与首次渲染一致,没什么特殊处理

completeWork()

当fiber可以被复用时,它的stateNode也可以被复用,在completeWork中,通过判断 workInProgress.stateNode !== null 阻止重新创建DOM?直接更新Component??

当更新执行到 HostComponent 时,会直接触发 updateComponent 的处理方法

ts
function completeWork() {
  // 存储了children数据!!可以看文章reconcileSingleElement()的分析
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case HostComponent: {
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode !== null) {
        // 更新逻辑
        updateHostComponent(current, workInProgress, type, newProps);
      } else {
        const instance = createInstance(type); // 创建dom
        appendAllChildren(instance, workInProgress); //
        workInProgress.stateNode = instance;
        finalizeInitialChildren(instance, type);
      }
      bubbleProperties(workInProgress);
      return null;
    }
  }
}

updateComponent 的逻辑也很简单,就是进行所有属性的比对,然后赋值到 updateQueue,进行 Update 的标记!

ts
updateHostComponent$1 = function () {
  var oldProps = current.memoizedProps;
  if (oldProps === newProps) {
    return;
  }
  var instance = workInProgress.stateNode;
  var updatePayload = prepareUpdate(instance, type, oldProps, newProps);
  // 最终得到 updatePayload = ["children", "45"]
  // updatePayload = 属性 + 新的值
  workInProgress.updateQueue = updatePayload;
  if (updatePayload) {
    markUpdate(workInProgress);
  }
};
function prepareUpdate() {
  // diffProperties: 复杂的属性比对,根据style...等各种情况比对是否已经改变
  return diffProperties(domElement, type, oldProps, newProps);
}
function markUpdate(workInProgress) {
  workInProgress.flags |= Update;
}

commitMutationEffectsOnFiber()

ts
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
    case HostComponent: {
        recursivelyTraverseMutationEffects(root, finishedWork);
        commitReconciliationEffects(finishedWork);
        if (flags & Update) {
            var _instance4 = finishedWork.stateNode;
            if (_instance4 != null) {
                var newProps = finishedWork.memoizedProps; 
                var oldProps = current !== null ? current.memoizedProps : newProps;
                var type = finishedWork.type; 
                var updatePayload = finishedWork.updateQueue;
                finishedWork.updateQueue = null;
                if (updatePayload !== null) {
                    commitUpdate(_instance4, updatePayload, type, oldProps, newProps, finishedWork);
                }
            }
        }
        return;
    }
}
function commitUpdate() {
    // Apply the diff to the DOM node.
    updateProperties(domElement, updatePayload, type, oldProps, newProps); // Update the props handle so that we know which props are the ones with
    // with current event handlers.
    updateFiberProps(domElement, newProps);
}

FunctionComponent

beginWork()

先进行 oldProps 和 newProps 的比对,如果有变化则设置 didReceiveUpdate = true,如果没有变化,则设置为 didReceiveUpdate = false

然后触发 updateFunctionComponent()

ts
function beginWork() {
  if (current !== null) {
    var oldProps = current.memoizedProps;
    var newProps = workInProgress.pendingProps;

    if (oldProps !== newProps || hasContextChanged()) {
      didReceiveUpdate = true;
    } else {
      //...

      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        didReceiveUpdate = true;
      } else {
        didReceiveUpdate = false;
      }
    }
  } else {
    didReceiveUpdate = false;
    //...
  }

  switch (workInProgress.tag) {
    case FunctionComponent: {
      //...
      return updateFunctionComponent();
    }
  }
}

updateFunctionComponent() 中,渲染更新流程如果 didReceiveUpdate = false,说明没有变化,则不继续执行 diff 和生成 childrenFibers 的流程,也就是不需要该 fiber 进行渲染更新!

ts
function updateFunctionComponent() {
  prepareToReadContext(workInProgress, renderLanes);

  nextChildren = renderWithHooks();

  if (current !== null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  workInProgress.flags |= PerformedWork;
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}
function bailoutHooks(current, workInProgress, lanes) {
  workInProgress.updateQueue = current.updateQueue; 
  workInProgress.flags &= ~(Passive | Update);
  current.lanes = removeLanes(current.lanes, lanes);
}

completeWork()

与首次渲染一致,没什么特殊处理

commitMutationEffectsOnFiber()

根据 Update 的 flags 标记,触发

  • 清理旧的useEffect
  • 挂载新的useEffect(异步执行)
  • 清理旧的useLayoutEffect(同步执行)
ts
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
    recursivelyTraverseMutationEffects(root, finishedWork);
    commitReconciliationEffects(finishedWork);
    if (flags & Update) {
        commitHookEffectListUnmount(
            Insertion | HasEffect,
            finishedWork,
            finishedWork.return,
        );
        commitHookEffectListMount(Insertion | HasEffect, finishedWork);
        commitHookEffectListUnmount(
            Layout | HasEffect,
            finishedWork,
            finishedWork.return,
        );
    }
    return;
}

那 FunctionComponent 的 Update 标记是在什么地方打上的呢?

总结

切换双缓冲树流程

createContainer()

  1. 创建 root = new FiberRootNode()对象(一个非常普通的对象具有一些特定的属性)
  2. 创建 uninitializedFiber = new FiberNode()对象(一个非常普通的对象具有一些特定的属性)
  3. FiberRootNodeFiberNode相互绑定
  4. 初始化root对应fiberstate对象,放入fiber.memoizedState
  5. initializeUpdateQueue()fiber.updateQueue初始化(updateQueue也是一个非常普通的对象具有一些特定的属性)
javascript
function createContainer() {
  return createFiberRoot(...);
}
function createFiberRoot(...) {
  var root = new FiberRootNode(...);
  var uninitializedFiber = createHostRootFiber(tag, isStrictMode);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;
  //...
  var initialState = {...};
  uninitializedFiber.memoizedState = initialState;

  initializeUpdateQueue(uninitializedFiber);
  return root;
}
function FiberRootNode(...) {
  this.tag = tag;
  this.containerInfo = containerInfo;
  //...
}
function createHostRootFiber() {
  return createFiber(HostRoot, null, null, mode);
}
var createFiber = function (...) {
  return new FiberNode(...);
}

双缓冲树是指在更新过程中,React会构建一个workInProgress树,当这个树构建完成后,会切换当前树指向它,从而提升渲染效率,避免中间状态导致的UI闪烁

React中的Fiber树结构,每个Fiber节点都有一个alternate属性,指向对应的另一个树的节点,这样在更新时就可以交替使用

双缓冲树的工作流程

  1. 构建阶段:React 在 workInProgress 树上进行异步渲染(Concurrent Mode)。
  2. 提交准备:当 workInProgress 树构建完成(finishedWork),进入提交阶段。
  3. DOM 更新:在 Mutation Phase 应用所有 DOM 变更。
  4. 切换指针:通过 root.current = finishedWork 将 current 树指向新树。
  5. 回调执行:在 Layout Phase 触发副作用回调,此时新树已生效。