React源码解析-Fiber构建-beginWork

23,231 阅读3分钟

上一篇,我们介绍了fiber的基本概念,以及fiber在初始化阶段生成了fiberRoot和rootFiber 2个对象。

但,整个fiber树还没有构建,未进入reconciler阶段。

本篇,我们将介绍,fiber链表的构建之-beginWork阶段

一. scheduleUpdateOnFiber

function scheduleUpdateOnFiber(fiber, lane, eventTime) {
  // ...
  if (root === workInProgressRoot) {
      {
        workInProgressRootUpdatedLanes = mergeLanes(workInProgressRootUpdatedLanes, lane);
      }
    }
  // ...
  if (lane === SyncLane) {
      if ( // Check if we're inside unbatchedUpdates
      (executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering
      (executionContext & (RenderContext | CommitContext)) === NoContext) {
        // ...
        performSyncWorkOnRoot(root)
      }else {
        // ...
        ensureRootIsScheduled(root, eventTime);
      }
  }else {
    // ...
     ensureRootIsScheduled(root, eventTime);
     schedulePendingInteractions(root, lane);
  }
}

fiber在内存中,会有两份数据,一个是当前的,一个是在内存中正在构建的。

这里根据不同的启动模式,进行下面的协调阶段。在17版本中,一般使用sync模式。18版本默认开启并发模式。

二. performSyncWorkOnRoot

同步模式下的流程,如果是并发模式,会进入schedule异步调度,最终还会执行performSyncWorkOnRoot。

function performSyncWorkOnRoot(root) {
    flushPassiveEffects();
    var lanes;
    var exitStatus;

    if (root === workInProgressRoot && includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)) {
    
      lanes = workInProgressRootRenderLanes;
      exitStatus = renderRootSync(root, lanes);

      if (includesSomeLane(workInProgressRootIncludedLanes, workInProgressRootUpdatedLanes)) {
      
        lanes = getNextLanes(root, lanes);
        exitStatus = renderRootSync(root, lanes);
      }
    } else {
      lanes = getNextLanes(root, NoLanes);
      exitStatus = renderRootSync(root, lanes);
    }
    // ...
    var finishedWork = root.current.alternate;
    root.finishedWork = finishedWork;
    root.finishedLanes = lanes;
    commitRoot(root); 

    ensureRootIsScheduled(root, now());
    return null;
}

performSyncWorkOnRoot是reconciler阶段所有的执行入口,首次渲染将进入renderRootSync。

问题来了,为什么要先执行flushPassiveEffects?这里留个悬念,在后续的更新流程中我们再提及。

关于nextLanes,这里我先抛开,先理解为render优先级,lane模型会在后续章节系统性的讲解。

renderRootSync

function renderRootSync(root, lanes) {
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
      prepareFreshStack(root, lanes); 
      startWorkOnPendingInteractions(root, lanes);
    }
    
   // ...
    do {
      try {
        workLoopSync();
        break;
      } catch (thrownValue) {
        handleError(root, thrownValue);
      }
    } while (true);
    // ...
}

prepare阶段,可以构建双缓存fiber,即workInProgressRoot,内存中的fiber通过之前的createFiber方法调用,当前fiber和内存中fiber通过alternate相互引用。

workLoopSync即react两大工作循环中的第一层循环,从这里开始构建fiber链表。

workLoopSync

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

这里是同步构建的情况,值得对比的是另外一个方法:

function workLoopConcurrent() {
    while (workInProgress !== null && !shouldYield()) {
      performUnitOfWork(workInProgress);
    }
  }

此方法是并发模式下的工作模式,两者区别在于shouldYield。shouldYield由schedule调度器控制,react自己实现了一套浏览器空闲时的任务调度。 其实,浏览器本身有对应的api:requestIdCallback。但不同浏览器执行有时间差异,不能满足react设计需要。

performUnitOfWork

function performUnitOfWork(unitOfWork) {
    var current = unitOfWork.alternate; 
    // ...
    var next;
    
    if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
      startProfilerTimer(unitOfWork);
      next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
      stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
    } else {
      next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
    }
    
    resetCurrentFiber();
    unitOfWork.memoizedProps = unitOfWork.pendingProps;

    if (next === null) {
      completeUnitOfWork(unitOfWork);
    } else {
      workInProgress = next;
    }

    ReactCurrentOwner$2.current = null;
  }

profile是react内部性能跟踪调试器,在正常的开发生产环境不会主动开启,将进入beginWork阶段

三. beginWork

  function beginWork(current, workInProgress, renderLanes) {
      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 )) {
        didReceiveUpdate = true;
      } else if (!includesSomeLane(renderLanes, updateLanes)) {
        didReceiveUpdate = false; 

        switch (workInProgress.tag) {
          // ...
        }
      } else {
        if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
   
          didReceiveUpdate = true;
        } else {
          
          didReceiveUpdate = false;
        }
      }
    } else {
 
      didReceiveUpdate = false;
    } 
    
    workInProgress.lanes = NoLanes;
    switch (workInProgress.tag) {
      case IndeterminateComponent:
        {
          return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
        }

      case LazyComponent:
        {
          var elementType = workInProgress.elementType;
          return mountLazyComponent(current, workInProgress, elementType, updateLanes, renderLanes);
        }

      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 HostText:
        return updateHostText(current, workInProgress);

      case SuspenseComponent:
        return updateSuspenseComponent(current, workInProgress, renderLanes);

      case HostPortal:
        return updatePortalComponent(current, workInProgress, renderLanes);

      case ForwardRef:
        {
          var type = workInProgress.type;
          var _unresolvedProps2 = workInProgress.pendingProps;

          var _resolvedProps2 = workInProgress.elementType === type ? _unresolvedProps2 : resolveDefaultProps(type, _unresolvedProps2);

          return updateForwardRef(current, workInProgress, type, _resolvedProps2, renderLanes);
        }

      case Fragment:
        return updateFragment(current, workInProgress, renderLanes);

      case Mode:
        return updateMode(current, workInProgress, renderLanes);

      case Profiler:
        return updateProfiler(current, workInProgress, renderLanes);

      case ContextProvider:
        return updateContextProvider(current, workInProgress, renderLanes);

      case ContextConsumer:
        return updateContextConsumer(current, workInProgress, renderLanes);

      case MemoComponent:
        {
          var _type2 = workInProgress.type;
          var _unresolvedProps3 = workInProgress.pendingProps; // Resolve outer props first, then resolve inner props.

          var _resolvedProps3 = resolveDefaultProps(_type2, _unresolvedProps3);

          {
            if (workInProgress.type !== workInProgress.elementType) {
              var outerPropTypes = _type2.propTypes;

              if (outerPropTypes) {
                checkPropTypes(outerPropTypes, _resolvedProps3, // Resolved for outer only
                'prop', getComponentName(_type2));
              }
            }
          }

          _resolvedProps3 = resolveDefaultProps(_type2.type, _resolvedProps3);
          return updateMemoComponent(current, workInProgress, _type2, _resolvedProps3, updateLanes, renderLanes);
        }

      case SimpleMemoComponent:
        {
          return updateSimpleMemoComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, updateLanes, renderLanes);
        }

      case IncompleteClassComponent:
        {
          var _Component3 = workInProgress.type;
          var _unresolvedProps4 = workInProgress.pendingProps;

          var _resolvedProps4 = workInProgress.elementType === _Component3 ? _unresolvedProps4 : resolveDefaultProps(_Component3, _unresolvedProps4);

          return mountIncompleteClassComponent(current, workInProgress, _Component3, _resolvedProps4, renderLanes);
        }

      case SuspenseListComponent:
        {
          return updateSuspenseListComponent(current, workInProgress, renderLanes);
        }

      case FundamentalComponent:
        {

          break;
        }

      case ScopeComponent:
        {

          break;
        }

      case Block:
        {
          {
            var block = workInProgress.type;
            var props = workInProgress.pendingProps;
            return updateBlock(current, workInProgress, block, props, renderLanes);
          }
        }

      case OffscreenComponent:
        {
          return updateOffscreenComponent(current, workInProgress, renderLanes);
        }

      case LegacyHiddenComponent:
        {
          return updateLegacyHiddenComponent(current, workInProgress, renderLanes);
        }
    }

  }

其中 didReceiveUpdate会在更新ref时使用到,也是props是否有变化的标志,这里先不关注。

首次将进入rootFiber的case,即HostRoot

updateHostRoot

function updateHostRoot(current, workInProgress, renderLanes) {
  // ...
  // 服务器端渲染处理先省略...
  // ...
  
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

reconcileChildren

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
   
    if (current === null) {
      // If this is a fresh new component that hasn't been rendered yet, we
      // won't update its child set by applying minimal side-effects. Instead,
      // we will add them all to the child before it gets rendered. That means
      // we can optimize this reconciliation pass by not tracking side-effects.
      workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
    } else {
      // If the current child is the same as the work in progress, it means that
      // we haven't yet started any work on these children. Therefore, we use
      // the clone algorithm to create a copy of all the current children.
      // If we had any progressed work already, that is invalid at this point so
      // let's throw it out.
      workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
    }
  }

对于首次渲染,current为Null,进入reconcileChildFibers

reconcileChildFibers

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
  var isObject = typeof newChild === 'object' && newChild !== null;

      if (isObject) {
        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);
            }

        }
      }
}

这里newChild是element对象,即render初始化阶段所生成的react element。

这里,我们可以看到typeof了,除了是个symbol,防止伪造攻击外,对于不同的类型,会有不同的fiber构建流程。普通的typeof了,除了是个symbol,防止伪造攻击外,对于不同的类型,会有不同的fiber构建流程。普通的typeof是element type。

reconcileSingleElement

function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
      var key = element.key;
      var child = currentFirstChild;
      
      // ... 如果存在child,递归删除
      // ...
      
      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;
      }
}

createFiberFromElement

function createFiberFromElement(element, mode, lanes) {
    var owner = null;

    {
      owner = element._owner;
    }

    var type = element.type;
    var key = element.key;
    var pendingProps = element.props;
    var fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes);

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

    return fiber;
  }

根据element对象创建子节点fiber树,并设置构建的子fiber.return为父fiber。遍历的方式使用的是深度优先遍历算法,一边对子节点做fiber实例化,一边对节点的上下关系做绑定

mountIndeterminateComponent

function mountIndeterminateComponent(_current, workInProgress, Component, renderLanes) {
  // ...
  prepareToReadContext(workInProgress, renderLanes);
  // ...
  
  setIsRendering(true);
  ReactCurrentOwner$1.current = workInProgress;

  value = renderWithHooks(null, workInProgress, Component, props, context, renderLanes);
  setIsRendering(false);
  // ...
  
  reconcileChildren(null, workInProgress, value, renderLanes);
  // ...
}

自定义组件,将先设置rendering状态以及全局的render fiber进行时对象,自定义组件内可能有副作用,比如useEffect,会影响flags。

renderWithHooks

function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
  // ...
  {
      if (current !== null && current.memoizedState !== null) {
        ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV;
      } else if (hookTypesDev !== null) {
        
        ReactCurrentDispatcher$1.current = HooksDispatcherOnMountWithHookTypesInDEV;
      } else {
        ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV;
      }
    }
    
    var children = Component(props, secondArg);
    
    ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
}

ReactCurrentDispatcher对象很重要,这是effect处理重要的全局对象,他将改变flags值,并影响后续的effect链表构建。

Component即函数组件对象,执行的结果即element对象。此element对象将再次调用reconcileChildren,进入协调child阶段,最终返回child的Fiber。

问题来了:

  1. fiber是一边生成,一边关联关系的,那么每层的fiber是如何找到下层element的?
  2. 不同的fiber对象,updateQueue都一样吗?
  3. 不同的fiber对象,memoizedState都一样吗?

对于rootFiber而言,updateQueue挂载的element对象,经过process update,清空updateQueue.shared,进而将element对象挂载至memoizedState上,当执行reconcileChildren时,nextChild从memoizedState获取。

对于nextChild为function组件时,包括顶层函数组件,将执行renderWithHooks,返回全量的element对象,当然renderWithHooks功能不仅仅于此,还涉及重要的flags计算。当执行reconcileChildren时,会将element挂载至下层fiber的pendingProps上。

对于nextChild为普通节点时,会根据层层根据pendingProps获取下一层节点的信息,从而继续构建fiber树。

值得注意的是,函数组件节点的updateQueue指的是lastEffect链表,他其实是一个环状链表结构。

每个节点的构建,都会设置memoizedProps = pendingProps

至此,beginWork的递归构建已完成,下面将进入completeWork