React源码渲染阶段-创建子Fiber

概览

React16.8版本之后, 使用Fiber提供了任务的优先级,中断可恢复的能力(开启CM模式). React通过Scheduler将高优先级的任务率先扔进Reconciler, Reconcile阶段创建了每个节点(其中可能经历复用或者diff等),生成Fiber树,并生成对应的dom树(暂未插入到页面),这个阶段我们称之为render阶段(渲染阶段)

React Fiber使用了双缓存机制, 提供了两棵Fiber树, 当前展示的这棵树我们称之为current树(本次更新的上一次更新的树), 还有一棵是首次更新或者触发更新后在内存中生成的树, 我们称之为WorkInProgress

双缓存

current树: current树上的每一个工作单元展示了当前页面的dom情况, 首屏渲染时, current树只存在应用程序的根rootFiber节点

workInProgress树: workInProgress树上的每一个工作单元的形成会在内存中执行, 渲染mount或者update时的更新逻辑

具体可以参考卡老师写的React揭秘中理念篇的Fiber结构的工作原理,来详细的理解双缓存的概念——什么是双缓存

渲染阶段

render阶段开始于performSyncWorkOnRootperformConcurrentWorkOnRoot,取决于当前的模式. 这个函数是React整个流程,包括render阶段commit阶段

源码起点.png

上图红框标出三个断点, 第一个断点是render阶段的核心

function renderRootSync(root, lanes) {
  // ......
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    {
      if (isDevToolsPresent) {
        var memoizedUpdaters = root.memoizedUpdaters;

        if (memoizedUpdaters.size > 0) {
          restorePendingUpdaters(root, workInProgressRootRenderLanes);
          memoizedUpdaters.clear();
        } // At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
        // If we bailout on this work, we'll move them back (like above).
        // It's important to move them now in case the work spawns more work at the same priority with different updaters.
        // That way we can keep the current update and future updates separate.


        movePendingFibersToMemoized(root, lanes);
      }
    }
    // 重置调度队列,并从root节点(新的高优先级的节点)开始调度
    prepareFreshStack(root, lanes);
  }

  {
    markRenderStarted(lanes);
  }

  do {
    try {
      // 尝试循环创建工作单元
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);

 // .....
  return workInProgressRootExitStatus;
} // Th
复制代码

这个方法最重要的两个方法, 一个是prepareFreshStack, 这个方法是用于重置调度队列,并从root节点(新的高优先级的节点)开始调度, 这个函数内部调用了createWorkInProgress, 我们放到下面一会讲

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

// performConcurrentWorkOnRoot会调用该方法
function workLoopConcurrent() { 
  //和上面的区别在于shouldYield, 这个代表是否存在剩余时间
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}
复制代码

还有一个是workLoopSync, 循环创建调用performUnitOfWork, 通过函数名可以看到是循环执行工作单元, 为遍历到的每个Fiber节点提供beginWork


function performUnitOfWork(unitOfWork) {
  var current = unitOfWork.alternate;
  setCurrentFiber(unitOfWork);
  var next;
  if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    // 开始beginWork阶段, 主要是创建当前节点的子Fiber节点
    next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
  }

  resetCurrentFiber();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    // 如果子节点为null, 那么直接把当前节点completeWork
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner$2.current = null;
}
复制代码

beginWork的主要作用在于创建当前节点的子Fiber节点, 该方法为render阶段递归阶段阶段的主要方法

beginWork

function beginWork(current, workInProgress, renderLanes) {
  var updateLanes = workInProgress.lanes;
  {
    if (workInProgress._debugNeedsRemount && current !== null) {
      // This will restart the begin phase with a new fiber.
      return remountFiber(current, workInProgress, createFiberFromTypeAndProps(workInProgress.type, workInProgress.key, workInProgress.pendingProps, workInProgress._debugOwner || null, workInProgress.mode, workInProgress.lanes));
    }
  }

  // 当前的workInProgress树上正在工作的单元对应的alternate对应的Fiber节点
  if (current !== null) {
    var oldProps = current.memoizedProps;
    var newProps = workInProgress.pendingProps;

    if (oldProps !== newProps || hasContextChanged() || ( // Force a re-render if the implementation changed due to hot reload:
     workInProgress.type !== current.type )) {
       // props或者context变化, 有更新的逻辑
      didReceiveUpdate = true;
    } else if (!includesSomeLane(renderLanes, updateLanes)) {
      didReceiveUpdate = false; 

      // 这里主要是针对不接受更新的ReactElement对象, 进行复用, 针对不同的类型传递一些属性
      switch (workInProgress.tag) { 
        case HostRoot:
          // ...
        case // ...
      }

      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    } else {
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        // 这个解决了context改变的时候suspense没有触发更新的问题
        didReceiveUpdate = true;
      } else {
        didReceiveUpdate = false;
      }
    }
  } else {
    didReceiveUpdate = false;
  }

  workInProgress.lanes = NoLanes;

  switch (workInProgress.tag) {
    // 根据tag判断当前工作单元的类型, 基于当前类型进入不同的逻辑
    case IndeterminateComponent:
      // ...
    case LazyComponent:
      // ...

    case FunctionComponent:
      {
        var _Component = workInProgress.type;
        var unresolvedProps = workInProgress.pendingProps;
        var resolvedProps = workInProgress.elementType === _Component ? unresolvedProps : resolveDefaultProps(_Component, unresolvedProps);
        return updateFunctionComponent(current, workInProgress, _Component, resolvedProps, renderLanes);
      }

    case ClassComponent:
      {
        var _Component2 = workInProgress.type;
        var _unresolvedProps = workInProgress.pendingProps;

        var _resolvedProps = workInProgress.elementType === _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);

        return updateClassComponent(current, workInProgress, _Component2, _resolvedProps, renderLanes);
      }

    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);

    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);

    case xxxx:
      // ..........
  }
}
复制代码

我们可以看到beginWork主要做了几件事情

  1. 根据current判断这个工作单元能否复用
  2. 如果不能复用则更新, 根据当前工作单元的类型决定进入什么逻辑
  • 工作单元类型可以在src/react-reconciler/ReactWorkTags.js中找到, div属于HostComponent

beginWork中的更新逻辑(以div举例)

如果当前工作单元是div, 那么会进入updateHostComponent的更新逻辑

function updateHostComponent(current, workInProgress, renderLanes) {
  pushHostContext(workInProgress);

  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }

  var type = workInProgress.type;
  var nextProps = workInProgress.pendingProps;
  var prevProps = current !== null ? current.memoizedProps : null;
  var nextChildren = nextProps.children;
  var isDirectTextChild = shouldSetTextContent(type, nextProps);
  // 文本节点的优化, 当div下只有一个单独的文本节点,那么react不会单独为这个Fiber节点创建子节点
  if (isDirectTextChild) {
    nextChildren = null;
  } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
    workInProgress.flags |= ContentReset;
  }

  markRef(current, workInProgress);
  // reconcileChildren是diff子节点, 如果current是null, 说明是mount, 否则是update
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}
复制代码

针对div的更新, 主要调用了reconcileChilren的方法, 以下是reconcileChilren方法

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
    // mount时
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
    // update时
    workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
  }
}
复制代码

reconcileChilrenmount时调用了mountChildFibers, 在update时调用了reconcileChildFibers, 这两个方法都是调用的ChildReconciler, 返回一个节点并赋值给workInProgress.child, 因此可以验证beginWork的目的是创建当前节点的子Fiber 节点

var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);
复制代码

ChildReconciler的参数的含义代表的是是否需要追踪副作用, mount时不需要追踪副作用,原因是我们只需要被插入一次, 如果追踪副作用, 那么每个节点都将被打上effectTagPlacement(插入), 这样commit阶段所有节点都会被插入一次, 这种频繁操作dom的行为显然是消耗性能且没有必要的

function ChildReconciler(shouldTrackSideEffects) {
  // ......其他节点的diff算法
  // 这里只展示单一节点的diff算法的源码
  function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
    var key = element.key;
    var child = currentFirstChild;

    while (child !== null) {
      // TODO: If key === null and child.key === null, then this only applies to
      // the first item in the list.
      if (child.key === key) {
        var elementType = element.type;

        if (elementType === REACT_FRAGMENT_TYPE) {
          if (child.tag === Fragment) {
            deleteRemainingChildren(returnFiber, child.sibling);
            var existing = useFiber(child, element.props.children);
            existing.return = returnFiber;

            {
              existing._debugSource = element._source;
              existing._debugOwner = element._owner;
            }

            return existing;
          }
        } else {
          if (child.elementType === elementType || ( // Keep this check inline so it only runs on the false path:
           isCompatibleFamilyForHotReloading(child, element) ) || // Lazy types should reconcile their resolved type.
          // We need to do this after the Hot Reloading check above,
          // because hot reloading has different semantics than prod because
          // it doesn't resuspend. So we can't let the call below suspend.
           typeof elementType === 'object' && elementType !== null && elementType.$$typeof === REACT_LAZY_TYPE && resolveLazy(elementType) === child.type) {
            deleteRemainingChildren(returnFiber, child.sibling);

            var _existing = useFiber(child, element.props);

            _existing.ref = coerceRef(returnFiber, child, element);
            _existing.return = returnFiber;

            {
              _existing._debugSource = element._source;
              _existing._debugOwner = element._owner;
            }

            return _existing;
          }
        } // Didn't match.


        deleteRemainingChildren(returnFiber, child);
        break;
      } else {
        deleteChild(returnFiber, child);
      }

      child = child.sibling;
    }

    if (element.type === REACT_FRAGMENT_TYPE) {
      var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key);
      created.return = returnFiber;
      return created;
    } else {
      var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);

      _created4.ref = coerceRef(returnFiber, currentFirstChild, element);
      _created4.return = returnFiber;
      return _created4;
    }
  }

  function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
    var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;

    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children;
    } // Handle object types
    if (typeof newChild === 'object' && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));

        case REACT_PORTAL_TYPE:
          return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes));

        case REACT_LAZY_TYPE:
          {
            var payload = newChild._payload;
            var init = newChild._init; // TODO: This function is supposed to be non-recursive.

            return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);
          }

      }
    }
    // return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

  return reconcileChildFibers;
}

复制代码

手动执行ChildReconciler会返回reconcileChildFibers, 这里巧妙的使用了闭包, 根据newChild中的$$typeof去进行不同逻辑.
参数的newChild可以看到向上追溯看到是当前workInProgress.pendingProps.children, 也就是当前工作单元props上的children
reconcileSingleElement代码已经展示在上面, 是单一节点的diff算法, 等讲到diff算法时在详细解读

function placeSingleChild(newFiber) {
  // This is simpler for the single child case. We only need to do a
  // placement for inserting new children.
  if (shouldTrackSideEffects && newFiber.alternate === null) {
    newFiber.flags |= Placement;
  }

  return newFiber;
}
复制代码

placeSingleChild就是为新节点添加Placement操作(mount时不添加), 并将其返回并添加到workInProgress.child

以上为beginWork的完整流程, 目的在于创建当前工作单元的第一个子Fiber节点


接下来补充一点逻辑, 为了方便调试, 并解决一些个人遇到的疑难困惑点

bailoutOnAlreadyFinishedWork

function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
  if (current !== null) {
    workInProgress.dependencies = current.dependencies;
  }
  {
    stopProfilerTimerIfRunning();
  }
  markSkippedUpdateLanes(workInProgress.lanes); // Check if the children have any pending work.

  if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
    {
      return null;
    }
  cloneChildFibers(current, workInProgress);
  return workInProgress.child;
}
复制代码

这个函数的作用在于复用Fiber节点, 函数调用的时机仅仅会发生在update时, 根据双缓存机制, 当更新时, current树已经存在对应的Fiber节点, 在上文beginWork中有以下这么一段代码, 我们可以看到当current树不为null并且新旧Props contexttype无变化的时候, 会复用节点(即return bailoutOnAlreadyFinishedWork),bailoutOnAlreadyFinishedWork调用cloneChildFibers

  // 当前的workInProgress树上正在工作的单元对应的alternate对应的Fiber节点
  if (current !== null) {
    var oldProps = current.memoizedProps;
    var newProps = workInProgress.pendingProps;

    if (oldProps !== newProps || hasContextChanged() || (
     workInProgress.type !== current.type )) {
       // props或者context变化, 有更新的逻辑
      didReceiveUpdate = true;
    } else if (!includesSomeLane(renderLanes, updateLanes)) {
      didReceiveUpdate = false; 

      // 这里主要是针对不接受更新的ReactElement对象, 进行复用, 针对不同的类型传递一些属性
      switch (workInProgress.tag) { 
        case HostRoot:
          // ...
        case // ...
      }

      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    } else {
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        // 这个解决了context改变的时候suspense没有触发更新的问题
        didReceiveUpdate = true;
      } else {
        didReceiveUpdate = false;
      }
    }
  } else {
    didReceiveUpdate = false;
  }
复制代码

cloneChildFibers

function cloneChildFibers(current, workInProgress) {
  if (!(current === null || workInProgress.child === current.child)) {
    {
      throw Error( "Resuming work not yet implemented." );
    }
  }

  if (workInProgress.child === null) {
    return;
  }

  var currentChild = workInProgress.child;
  var newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
  workInProgress.child = newChild;
  newChild.return = workInProgress;

  while (currentChild.sibling !== null) {
    currentChild = currentChild.sibling;
    newChild = newChild.sibling = createWorkInProgress(currentChild, currentChild.pendingProps);
    newChild.return = workInProgress;
  }

  newChild.sibling = null;
} 
复制代码

从名称可以读出, 这个函数的作用就是clone当前工作单元的子Fiber节点, 那么复用节点目的就是把current树上对应的现在Fiber节点的子Fiber节点保存到当前工作单元的child属性下, 这个方法会调用createWorkInProgress, 所以createWorkInProgress创建的不是当前工作单元, 而是当前工作单元的子工作单元

createWorkInProgress

function createWorkInProgress(current, pendingProps) {
  var workInProgress = current.alternate;

  if (workInProgress === null) {
    // 如果workInProgress是null, 那么为其创建一个Fiber节点
    workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;

    // ...省略部分赋值

    // 连接两课Fiber树
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type.

    workInProgress.type = current.type; // We already have an alternate.
    // Reset the effect tag.

    workInProgress.flags = NoFlags; // The effects are no longer valid.

    workInProgress.subtreeFlags = NoFlags;
    workInProgress.deletions = null;

    // ...省略部分代码

  } 

  workInProgress.flags = current.flags & StaticMask;
  workInProgress.childLanes = current.childLanes;
  workInProgress.lanes = current.lanes;
  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so
  // it cannot be shared with the current fiber.

  var currentDependencies = current.dependencies;
  workInProgress.dependencies = currentDependencies === null ? null : {
    lanes: currentDependencies.lanes,
    firstContext: currentDependencies.firstContext
  }; // These will be overridden during the parent's reconciliation

  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;

  // ...省略部分代码

  return workInProgress;
}
复制代码

createWorkInProgress从名称可以看出作用在于创建workInProgress节点, 这个函数的调用时机在cloneChildFibers或者prepareFreshStack中. 这个函数首先判断current.alternate是否为空, 这里解释一下, current.alternate为空说明当前工作单元的子工作单元是更新后新创建的, 这个时候我们会为新的工作单元创建一个Fiber节点, 否则我们对子工作单元进行一个赋值操作即可

全流程

这里梳理一下上述所说的全流程

React-render阶段_递阶段完整流程.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享