react学习系列—— render

130 阅读5分钟
/* 子组件2 */
function Child2(){
  return <div>子组件 2</div>
}
/* 子组件1 */
function Child1(){
  const [ num , setNumber ] = useState(0)
  return <div>
      子组件 {num}
      <button onClick={() => setNumber(num+1)} >按钮1</button>
   </div>
}
/* 父组件 */
export default function Index(){
  const [ num , setNumber ] = useState(0)
  return <div>
      <p>父组件 {num} </p>
      <Child1 />
      <Child2 />
      <button onClick={()=> setNumber(num+1)} >按钮2</button>
  </div>
}

点击child1的按钮1触发click事件,setNumber会执行scheduleUpdateOnFiber触发更新

截屏2024-03-20 10.51.01.png

fiber是child1, lane是2

之后进入markRootUpdated(root, lane)将FiberRootNode.pendingLanes = 2

addFiberToLanesMap(root, fiber, lane);将child fiber 存入将FiberRootNode.pendingUpdatersLaneMap

然后是ensureRootIsScheduled(root);

会在微任务阶段执行更新任务

截屏2024-03-20 09.48.47.png

还是在宏任务继续执行click的回调,把dispatchQueue执行完后,会进入flushSyncWorkAcrossRoots_impl(true)

由于onlyLegacy = true,没有进入performSyncWorkOnRoot

再把currentUpdatePriority还原,等宏任务执行完了就执行微任务

截屏2024-03-20 14.34.57.png

scheduleTaskForRootDuringMicrotask会判断是否是Concurrent模式还是legacy,这里是legacy模式,继续往下执行flushSyncWorkAcrossRoots_impl(false),这里就会进入到performSyncWorkOnRoot

截屏2024-03-20 14.43.36.png nextLanes = 2

performSyncWorkOnRoot主要是处理render和commit,并且在最后会又执行一次ensureRootIsScheduled

render: 截屏2024-03-20 14.47.54.png

commit:

commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions, workInProgressDeferredLane); 

renderRootSync

function renderRootSync(root, lanes) {
    movePendingFibersToMemoized(root, lanes); // 将pendingUpdatersLaneMap存放的child1 fiber放入memoizedUpdaters
    prepareFreshStack(root, lanes); // 更新workInProgress
    // finishQueueingConcurrentUpdates 更新fiber的childLanes
    
    workLoopSync() // 递归处理fiber,如果没有处理的fiber,
}
function finishQueueingConcurrentUpdates() { // setNumber会将dispatch放入concurrentQueues
  var endIndex = concurrentQueuesIndex;
  concurrentQueuesIndex = 0;
  concurrentlyUpdatedLanes = NoLanes;
  var i = 0;

  while (i < endIndex) {
    var fiber = concurrentQueues[i];
    concurrentQueues[i++] = null;
    var queue = concurrentQueues[i];
    concurrentQueues[i++] = null;
    var update = concurrentQueues[i];
    concurrentQueues[i++] = null;
    var lane = concurrentQueues[i];
    concurrentQueues[i++] = null;

    if (queue !== null && update !== null) {// update处理成循环队列
      var pending = queue.pending;

      if (pending === null) {
        // This is the first update. Create a circular list.
        update.next = update;
      } else {
        update.next = pending.next;
        pending.next = update;
      }

      queue.pending = update;
    }

    if (lane !== NoLane) {
      markUpdateLaneFromFiberToRoot(fiber, update, lane);// child1 fiber往上的parent.childLanes = 2,alternate也更新
    }
  }
}

从root开始遍历fiber

function workLoopSync() {
  // Perform work without checking if we need to yield between fiber.
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}
function performUnitOfWork(unitOfWork) {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  var current = unitOfWork.alternate;
  setCurrentFiber(unitOfWork);
  var next;

  if ((unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, entangledRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, entangledRenderLanes);
  }

  resetCurrentFiber(); // 这个fiber处理完成了
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner$1.current = null;
}
function beginWork$1(current, workInProgress, renderLanes) {
  {
    if (workInProgress._debugNeedsRemount && current !== null) {
      return remountFiber(current, workInProgress, createFiberFromTypeAndProps(workInProgress.type, workInProgress.key, workInProgress.pendingProps, workInProgress._debugSource || null, workInProgress._debugOwner || null, workInProgress.mode, workInProgress.lanes));
    }
  }

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

    if (oldProps !== newProps || hasContextChanged() || (
    workInProgress.type !== current.type )) {
      didReceiveUpdate = true;
    } else {
      var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes);

      if (!hasScheduledUpdateOrContext &&
      (workInProgress.flags & DidCapture) === NoFlags$1) {
        didReceiveUpdate = false;
        return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);
      }

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

    if (getIsHydrating() && isForkedChild(workInProgress)) {
      var slotIndex = workInProgress.index;
      var numberOfForks = getForksAtLevel();
      pushTreeId(workInProgress, numberOfForks, slotIndex);
    }
  }

  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
      //......
  }

root fiber的props没有发生变化,并且lanes是0,不等于2,会进入attemptEarlyBailoutIfNoScheduledUpdate

function attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes) {
    switch (workInProgress.tag) {
        // ......
    }
  return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
  if (current !== null) {
    // Reuse previous dependencies
    workInProgress.dependencies = current.dependencies;
  }

  {
    // Don't update "base" render times for bailouts.
    stopProfilerTimerIfRunning();
  }

  markSkippedUpdateLanes(workInProgress.lanes); // Check if the children have any pending work.

  if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
    // The children don't have any work either. We can skip them.
    // TODO: Once we add back resuming, we should check if the children are
    // a work-in-progress set. If so, we need to transfer their effects.
    {
      return null;
    }
  } // This fiber doesn't have work, but its subtree does. Clone the child
  // fibers and continue.


  cloneChildFibers(current, workInProgress);
  return workInProgress.child;
}

如果fiber.childLanes不在renderLanes里面,则跳过这个fiber

如果是之前markUpdateLaneFromFiberToRoot冒泡到过的fiber,则会进入cloneChildFibers

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

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

  var currentChild = workInProgress.child;
  var newChild = createWorkInProgress(currentChild, currentChild.pendingProps); // 如果没有alternate,会新建一个,如果有,则会根据current更新数据,注意newChild.child = currentChild.child
  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;
} // Reset a workInProgress child set to prepare it for a second pass.

经过冒泡的fiber会将child fibers经过更新从alternate迁移过来

之后将child fiber返回,回到performUnitOfWork,重置currentFiber,fiber.memoizedProps = fiber.pendingProps

workLoopSync处理 child fiber

注意cloneChildFibers拷贝当前层的child fiber,拷贝出来的child fiber的child的fiber仍然是current 这边的fiber,等到下一次处理的时候才会变成alternate的fiber。current和alternate的stateNode都指向同一个

beginWork child1

一直处理到child1 fiber,由于child1 fiber.lanes = 2,不会进入attemptEarlyBailoutIfNoScheduledUpdate,在beginWork继续往下走,走到updateFunctionComponent

function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) {
    ReactCurrentOwner$2.current = workInProgress;
    setIsRendering(true);
    nextChildren = renderWithHooks(current, workInProgress, Component, nextProps, context, renderLanes);
    hasId = checkDidRenderIdHook();
    setIsRendering(false);

  {
    markComponentRenderStopped();
  }

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

  if (getIsHydrating() && hasId) {
    pushMaterializedTreeId(workInProgress);
  } // React DevTools reads this flag.


  workInProgress.flags |= PerformedWork;
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
    var children = Component(props, secondArg);
}

var children = Component(props, secondArg);这步会重新运行Child1组件

const [ num , setNumber ] = useState(0)中num会取update里面最新的值,并把didReceiveUpdate = true

执行完child1后,children就是更新后的结果,可以看到div中的props第二个从0变到了1

截屏2024-03-21 10.42.17.png

之后进入reconcileChildren

reconcileChildren

workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);

workInProgress指的函数组件

workInProgress.child是函数组件的根元素

nextChildren是指jsx返回的element

reconcileChildren会先构建div fiber,然后将子element 放在peddingprops,之后再 beginWork div fiber之后,由于div的props是新建的,会进入updateHostRoot之后又会进入reconcileChildrenreconcileChildFibers再去reconcileChildrenArray批量构建sibling fiber

  function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
    // This indirection only exists so we can reset `thenableState` at the end.
    // It should get inlined by Closure.
    thenableIndexCounter$1 = 0;
    var firstChildFiber = reconcileChildFibersImpl(returnFiber, currentFirstChild, newChild, lanes);
    thenableState$1 = null; // Don't bother to reset `thenableIndexCounter` to 0 because it always gets
    // set at the beginning.

    return firstChildFiber;
  }
  function reconcileChildFibersImpl(returnFiber, currentFirstChild, newChild, lanes) {
  return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
  }

reconcileSingleElement会clone div fiber,将最新的pendingProps赋值给 div workInProgress, 然后child fiber连接div workInProgress

冒泡的fiber在bailoutOnAlreadyFinishedWork clone 更新的fiber在reconcileChildFibers clone

beginWork child1 div

之后performUnitOfWork div alternate,由于发生了变化,didReceiveUpdate = true,进入

updateHostComponent -> reconcileChildFibersImpl

由于div.child是数组,进入reconcileChildrenArray

截屏2024-03-21 11.12.01.png

reconcileChildrenArray中会进行updateTextNode对这些文本fiber进行更新(同样会进行clone)

beginWork child1 div '子组件'

由于lanes = 0,其实更新child div的时候也已经更新过了,返回null

completeWork child1 div '子组件'

进入到completeUnitOfWork completeWork

'子组件' complete之后,workInProgress指向当前fiber的sibling,也就是num

completeWork child1 div 'num'

在div的reconcileChildrenArray的时候,其child fiber都进行了clone,因此num fiber的pendingProps就在这时候变成了1

beginWork会进入updateHostText 返回null

unitOfWork.memoizedProps = unitOfWork.pendingProps;

进入completeWork

function updateHostText(current, workInProgress, oldText, newText) {
  {
    // If the text differs, mark it as an update. All the work in done in commitWork.
    if (oldText !== newText) {
      markUpdate(workInProgress);
    }
  }
}
function markUpdate(workInProgress) {
  workInProgress.flags |= Update;
}

Update = 4

num fiber flag被标记为了4

之后workInProgress指向button

completeWork child1 div button

进入completeWork

因为child1重新执行,所以current fiber 和 workInProgress 绑定的事件函数不是同一个,因此也被标记flag = 4

由于button没有sibling,workInProgress取div

completeWork child1 div

div的props由['子组件','0', button] => ['子组件','1', button] 发生了变化,也被打上flag=4的标记

workInProgress 取 child1

completeWork child1

type为函数直接继续冒泡,没有更新,没有标记

workInProgress 取 child2

beginWork child2

因为child2.childlanes = 0 没有被冒泡到,所以beginWork返回null

completeWork child2

type为函数直接继续冒泡,没有更新,没有标记

workInProgress 取 index button

beginWork button

completeWork button

......