react源码 递阶段mount时阶段

递阶段mount时流程

递阶段

首先从rootFiber开始向下深度优先遍历。为遍历到的每个Fiber节点调用beginWork方法 接起来。

当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。

打断点

  1. 运行React项目
  2. 进入开发者模式,点击Sources,搜索beiginWorkcompleteWork并打上断点

Fiber节点类型

  CacheComponent: 24, // Experimental
  ClassComponent: 1,
  ContextConsumer: 9,
  ContextProvider: 10,
  CoroutineComponent: -1, // Removed
  CoroutineHandlerPhase: -1, // Removed
  DehydratedSuspenseComponent: 18, // Behind a flag
  ForwardRef: 11,
  Fragment: 7,
  FunctionComponent: 0,
  HostComponent: 5,
  HostPortal: 4,
  HostRoot: 3, //  
  HostText: 6,
  IncompleteClassComponent: 17,
  IndeterminateComponent: 2,
  LazyComponent: 16,
  LegacyHiddenComponent: 23,
  MemoComponent: 14,
  Mode: 8,
  OffscreenComponent: 22, // Experimental
  Profiler: 12,
  ScopeComponent: 21, // Experimental
  SimpleMemoComponent: 15,
  SuspenseComponent: 13,
  SuspenseListComponent: 19, // Experimental
  TracingMarkerComponent: 25, // Experimental - This is technically in 18 but we don't
  // want to fork again so we're adding it here instead
  YieldComponent: -1, // Removed

调试的代码

import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App />
);

调试并观察

  1. 第一次执行beginWork函数

    参数:

    • current

      我们可以根据current.tag:3判断Fiber的类型,HostRoot当前应用的根节点

  2. 第二次执行beginWork函数
    参数:

    • current
      此时我们发现current = null,这是因为Fiber架构的双缓存机制,对于首次渲染,只有当前的根节点才有current,而其他节点只存在workProgress

    • workInProgress

      workInProgress.elementType:f App(),证明当前节点为App函数式组件

      workInProgress.pedndingProps,存储标签的属性(src,className,alt等)和子节点

  3. 第三次执行beginWorl函数

    参数:

    • workInProgress

      workInProgress.elementType:'div',证明当前节点为div

  4. workInProgress.elementType:'img'执行完毕后,因为<img/>标签下没有子节点,所以<img>节点会执行completeWork

  5. 执行p节点的beiginWork

  6. and save to reload.文本节点执行完beginWork后,本身没有子节点且没有下一个兄弟节点,所以会执行其父节点的completeWork

  7. 执行App的completeWork

  8. 执行HostRoot的completeWork

总结:

从根节点开始,执行beginWork函数,父节点执行完后,子节点执行

当节点无子节点,执行completeWork函数,该节点的下一个兄弟节点执行beginWork函数

当节点无子节点且无下一个兄弟节点,会执行其父节点的completeWork

注意:

<code>src/App.js</code>

<code>节点执行完beginWork函数后,不会进入src/App.js该文本节点的beginWork,而是进入co mpleteWorkworkInProgress.elementType:'code'

解释:

react对于只有唯一个文本子节点的节点做出了优化,在这种情况下,这种文本节点不会生成自己的Fiber节点

beginWork

执行过程(以<div>节点为例子):

  1. 根据当前workInProgress.tag进入不同的case

    switch (workInProgress.tag) {
        case IndeterminateComponent:
            {
                return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
            }
    
        case LazyComponent:
            {
                var elementType = workInProgress.elementType;
                return mountLazyComponent(current, workInProgress, elementType, 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 _Component = workInProgress.type;
                var _unresolvedProps = workInProgress.pendingProps;
    
                var _resolvedProps = workInProgress.elementType === _Component ? _unresolvedProps : resolveDefaultProps(_Component, _unresolvedProps);
    
                return updateClassComponent(current, workInProgress, _Component, _resolvedProps, renderLanes);
            }
    
        case HostRoot:
            return updateHostRoot(current, workInProgress, renderLanes);
    
        case HostComponent:
            return updateHostComponent(current, workInProgress, renderLanes);
    .....
    
  2. divtag:5,根据Fiber类型映射可得,div HostComponent类型,执行updateHostComponent函数

  3. updateHostComponent函数内部(部分)

    // 检测当前的fiber节点是否只有唯一的文本子节点,如 <code>src/App.js</code>
    var isDirectTextChild = shouldSetTextContent(type, nextProps);
    
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
    return workInProgress.child;
    

    在未执行reconcileChildren之前,workInPorgress.child = null

    • 进入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);
          }
      }
      

      我们查看源码,发现mountChildFibersreconcileChildFibers调用的同一个函数,只是参数不同

      export const reconcileChildFibers: ChildReconciler = createChildReconciler(true);
      export const mountChildFibers: ChildReconciler = createChildReconciler(false);
      
    • 进入createChildReconciler函数

      function createChildReconciler(shouldTrackSideEffects){
       ....   
      }
      

      shouldTrackSideEffects意为 是否追踪副作用

      react中通过flags记录每一个节点diff后需要变更的状态,例如:dom的增添、替换、删除等等

      // packages/react-reconciler/src/ReactFiberFlags.js
      
      // Don't change these two values. They're used by React Dev Tools.
      export const NoFlags = /*                      */ 0b00000000000000000000000000;
      export const PerformedWork = /*                */ 0b00000000000000000000000001;
      
      // You can change the rest (and add more).
      export const Placement = /*                    */ 0b00000000000000000000000010;
      export const Update = /*                       */ 0b00000000000000000000000100;
      export const ChildDeletion = /*                */ 0b00000000000000000000001000;
      export const ContentReset = /*                 */ 0b00000000000000000000010000;
      export const Callback = /*                     */ 0b00000000000000000000100000;
      export const DidCapture = /*                   */ 0b00000000000000000001000000;
      export const ForceClientRender = /*            */ 0b00000000000000000010000000;
      export const Ref = /*                          */ 0b00000000000000000100000000;
      export const Snapshot = /*                     */ 0b00000000000000001000000000;
      export const Passive = /*                      */ 0b00000000000000010000000000;
      export const Hydrating = /*                    */ 0b00000000000000100000000000;
      export const Visibility = /*                   */ 0b00000000000001000000000000;
      export const StoreConsistency = /*             */ 0b00000000000010000000000000;
      
      export const LifecycleEffectMask =
        Passive | Update | Callback | Ref | Snapshot | StoreConsistency;
      
      // Union of all commit flags (flags with the lifetime of a particular commit)
      export const HostEffectMask = /*               */ 0b00000000000011111111111111;
      
      // These are not really side effects, but we still reuse this field.
      export const Incomplete = /*                   */ 0b00000000000100000000000000;
      export const ShouldCapture = /*                */ 0b00000000001000000000000000;
      export const ForceUpdateForLegacySuspense = /* */ 0b00000000010000000000000000;
      export const DidPropagateContext = /*          */ 0b00000000100000000000000000;
      export const NeedsPropagation = /*             */ 0b00000001000000000000000000;
      export const Forked = /*                       */ 0b00000010000000000000000000;
      
      // Static tags describe aspects of a fiber that are not specific to a render,
      // e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
      // This enables us to defer more work in the unmount case,
      // since we can defer traversing the tree during layout to look for Passive effects,
      // and instead rely on the static flag as a signal that there may be cleanup work.
      export const RefStatic = /*                    */ 0b00000100000000000000000000;
      export const LayoutStatic = /*                 */ 0b00001000000000000000000000;
      export const PassiveStatic = /*                */ 0b00010000000000000000000000;
      
      // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
      export const PlacementDEV = /*                 */ 0b00100000000000000000000000;
      export const MountLayoutDev = /*               */ 0b01000000000000000000000000;
      export const MountPassiveDev = /*              */ 0b10000000000000000000000000;
      

      render阶段不会执行dom操作,具体的dom操作是在commit阶段执行的,render阶段需要做的是为需要执行dom操作的fiber节点打上标记,比如:这个fiber节点对应的dom节点需要插入在页面中,它会为fiber节点打上Placement标记

      为什么用二进制的形式来表示EffectTag?

      解释:

      const NoFlags = /*                      */ 0b00000000000000000000000000;
      const Placement = /*                    */ 0b00000000000000000000000010;
      const Update = /*                       */ 0b00000000000000000000000100;
      // 开始是没有副作用的
      let effectTag = NoFlags 
      // 打上标识
      effect |= Placement
      effect |= Update
      
    • 进入mountChildFibers函数

      1. 判断newChild的类型,根据不同的类型,执行不同的代码

        • 为对象类型且不为null

        • 为文本或数字

        • 为数组

      2. 因为div节点的newChild.$$typeof = REACT_ELEMENT_TYPE所以执行,reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes)

        function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes)

        • 判断currentFirstChild是否存在

          var child = currentFirstChild;
          
        • 判断该节点的子节点的类型并进入createFiberFromElement函数

              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创建fiber节点

          export function createFiberFromElement(
            element: ReactElement,
            mode: TypeOfMode,
            lanes: Lanes,
          ): Fiber {
            let owner = null;
            if (__DEV__) {
              owner = element._owner;
            }
            const type = element.type;
            const key = element.key;
            const pendingProps = element.props;
            const fiber = createFiberFromTypeAndProps(
              type,
              key,
              pendingProps,
              owner,
              mode,
              lanes,
            );
            if (__DEV__) {
              fiber._debugSource = element._source;
              fiber._debugOwner = element._owner;
            }
            return fiber;
          }
          
        • 进入createFiberFromTypeAndProps函数,此时传入的信息全是header节点的信息

          createFiberFromTypeAndProps函数中,typeheader,所以fiberTag = HostComponent

        • 进入createFiber函数

          /**
          * tag=5
          * pendingProps={
          * className:'App-header',
          * children:[{...},{...},{...}]
          * }
          */
          
          var createFiber = function (tag, pendingProps, key, mode) {
            return new FiberNode(tag, pendingProps, key, mode);
          };
          
        • 进入FiberNode,创建Fiber

      源代码

        function reconcileChildFibers(
          returnFiber: Fiber,
          currentFirstChild: Fiber | null,
          newChild: any,
          lanes: Lanes,
        ): Fiber | null {
          // This function is not recursive.
          // If the top level item is an array, we treat it as a set of children,
          // not as a fragment. Nested arrays on the other hand will be treated as
          // fragment nodes. Recursion happens at the normal flow.
      
          // Handle top level unkeyed fragments as if they were arrays.
          // This leads to an ambiguity between <>{[...]}</> and <>...</>.
          // We treat the ambiguous cases above the same.
          const 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:
                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 (isArray(newChild)) {
              return reconcileChildrenArray(
                returnFiber,
                currentFirstChild,
                newChild,
                lanes,
              );
            }
      
            if (getIteratorFn(newChild)) {
              return reconcileChildrenIterator(
                returnFiber,
                currentFirstChild,
                newChild,
                lanes,
              );
            }
      
            throwOnInvalidObjectType(returnFiber, newChild);
          }
      
          if (
            (typeof newChild === 'string' && newChild !== '') ||
            typeof newChild === 'number'
          ) {
            return placeSingleChild(
              reconcileSingleTextNode(
                returnFiber,
                currentFirstChild,
                '' + newChild,
                lanes,
              ),
            );
          }
      
          if (__DEV__) {
            if (typeof newChild === 'function') {
              warnOnFunctionType(returnFiber);
            }
          }
      
          // Remaining cases are all treated as empty.
          return deleteRemainingChildren(returnFiber, currentFirstChild);
        }
      
      beginWork流程图
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值