Loading...
墨滴

早睡wqi

2021/10/01  阅读:23  主题:默认主题

React render阶段解析二-beginWork流程

这次是接着上一期render阶段的文章,解析挂载时render阶段的"递"阶段----beginWork函数

beginWork的调用

performUnitOfWork函数内部执行beginWork的函数

// ProfileMode 模式下,猜测和性能分析相关
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    // 正常的流程
    // 已经使用的fiber, 更新的fiber,  subtreeRenderLanes: 当前的的lane
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }

beginWork$1函数

处理开发环境一些问题,还有执行beginWork和捕获问题。在开发环境执行performUnitOfWork会执行beginWork$1函数。

beginWork$1 = function (current, unitOfWork, lanes{
    // 如果一个组件抛出一个错误,我们会同步重放它
    // 分派事件,以便调试器将其视为未捕获
    // 错误 请参阅 ReactErrorUtils 了解更多信息。
    // 在进入开始阶段之前,将进行中的工作复制到虚拟对象上
    // Fiber"。如果 beginWork 抛出,我们将使用它来重置状态。
    // WorkInProgress的复制
    var originalWorkInProgressCopy = assignFiberPropertiesInDEV(dummyFiber, unitOfWork);

    try {
      // 调用beginWork
      return beginWork(current, unitOfWork, lanes);
    } catch (originalError) {
      // 捕获错误
    }
  };

beginWork主要函数

通过不断循环执行performUnitOfWork挂载子节点

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null 
{
  const updateLanes = workInProgress.lanes;

  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      // 如果实现因热重载而改变,则强制重新渲染
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
      // ...更新阶段的处理
    } else if (!includesSomeLane(renderLanes, updateLanes)) {
      // 
      //...
    } else {
      if ((current.effectTag & ForceUpdateForLegacySuspense) !== NoEffect) {
        
        didReceiveUpdate = false;
      }
    }
  } else {
  /* 该didReceiveUpdate变量代表本次更新中本Fiber节点是否有变化 */
    didReceiveUpdate = false;
  }
}

mount(首屏渲染) 时会根据不同的 workInProgress.tag(组件类型)来进入到不同的子节点创建逻辑

// 找到不同类型的组件
switch (workInProgress.tag) {
  case IndeterminateComponent: 
    // ReactDOM.render(<App />, document.getElementById("root")),挂载App组件
    return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderLanes,
      );
  case LazyComponent: 
    // ...省略
  case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
  case ClassComponent: 
    // ...省略
  case HostRoot:
    // 第一次创建根节点时
     return updateHostRoot(current, workInProgress, renderLanes);
  case HostComponent:
    // 创建根组件节点
    return updateHostComponent(current, workInProgress, renderLanes);
  case HostText:
    // ...省略
  // ...省略其他类型
}

创建根节点

第一循环进入的beginWork会去创建根节点Fiber,执行updateHostRoot函数

function updateHostRoot(current, workInProgress, renderLanes{
  // 压入fiber栈中和标记上下文,取html标签
  // 主要使用fiberStack和valueStatck,调用push和pop函数
  pushHostRootContext(workInProgress);
  // 获取workInProgress的相关属性
  const updateQueue = workInProgress.updateQueue;
  const nextProps = workInProgress.pendingProps;
  const prevState = workInProgress.memoizedState;
  const prevChildren = prevState !== null ? prevState.element : null;
  
  // 把current的更新队列赋值给workInProgress
  cloneUpdateQueue(current, workInProgress);
  // 更新updateQueue
  processUpdateQueue(workInProgress, nextProps, null, renderLanes);
  const nextState = workInProgress.memoizedState;
  // Caution: React DevTools currently depends on this property
  // being called "element".
  const nextChildren = nextState.element;
  if (nextChildren === prevChildren) {
    resetHydrationState();
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }
  const root: FiberRoot = workInProgress.stateNode;
  if (root.hydrate && enterHydrationState(workInProgress)) {
    // 服务端渲染
  } else {
    // Otherwise reset hydration state in case we aborted and resumed another
    // root.
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
    resetHydrationState();
  }
  return workInProgress.child;
}

从函数内部可以知道

  1. processUpdateQueue处理更新[1],会把workInProgress和current的更新队列都执行一遍
  2. reconcileChildren->reconcileChildFibers(执行diff算法)

reconcileChildFibers

diff算法主要入口

function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes,
  
): Fiber | null 
{
    // 
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null;
    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children;
    }

    // Handle object types
    const isObject = typeof newChild === 'object' && newChild !== null;

    if (isObject) {
      //  单节点diff
      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:
          if (enableLazyElements) {
            const payload = newChild._payload;
            const init = newChild._init;
            // TODO: This function is supposed to be non-recursive.
            return reconcileChildFibers(
              returnFiber,
              currentFirstChild,
              init(payload),
              lanes,
            );
          }
      }
    }

    if (typeof newChild === 'string' || typeof newChild === 'number') {
      return placeSingleChild(
        reconcileSingleTextNode(
          returnFiber,
          currentFirstChild,
          '' + newChild,
          lanes,
        ),
      );
    }

    // 多节点diff
    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }

    if (getIteratorFn(newChild)) {
      return reconcileChildrenIterator(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }

    if (isObject) {
      throwOnInvalidObjectType(returnFiber, newChild);
    }

    
    if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
      // If the new child is undefined, and the return fiber is a composite
      // component, throw an error. If Fiber return types are disabled,
      // we already threw above.
      switch (returnFiber.tag) {
        case ClassComponent: {
          if (__DEV__) {
            const instance = returnFiber.stateNode;
            if (instance.render._isMockFunction) {
              // We allow auto-mocks to proceed as if they're returning null.
              break;
            }
          }
        }
        
        case FunctionComponent: {
          const Component = returnFiber.type;
          
        }
      }
    }

    // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

总结

  1. 无论挂载还是更新的流程,beginWork阶段会通过workInProgress.tag去判断节点的tag值,根据不同节点去执行不同的函数
  2. beginWork->reconcileChildren->根据是否存在current树调用mountChildFibers或reconcileChildFibers
  3. mountChildFibers或reconcileChildFibers都是根据ChildReconciler返回的函数赋值
  4. 下一讲将看render阶段的"归"阶段completeWork

参考资料

[1]

扒一扒React计算状态的原理: https://segmentfault.com/a/1190000039008910。

早睡wqi

2021/10/01  阅读:23  主题:默认主题

作者介绍

早睡wqi