React18.2源码解析之render阶段(上)

411 阅读7分钟

记录学习过程,如有错误欢迎指出

前三篇文章我们已经讲完了React的初始化阶段,接下来就是render阶段了,这也是React核心调度开始

开始之前(必看)

React version 18.2.0

DEV代码可以忽略,下面正文我使用省略号就代表是dev相关的代码,包含hydrate字样的也请忽略,那是服务端渲染相关,望周知

我使用深度优先(🐶)的方式讲解:即遇到函数先进入函数,执行完函数后,又退回之前的函数.而不是在一个函数中讲完了再讲函数中执行的函数(希望能听懂我在说什么^v^)

因为使用了深度优先的方式讲解,耦合比较重,不建议跳着看

入口

//performSyncWorkOnRoot就是在sync模式下的入口
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));


//performConcurrentWorkOnRoot就是是concurrent模式下的入口
newCallbackNode = scheduleCallback( schedulerPriorityLevel, // concurrent模式下调用 performConcurrentWorkOnRoot.bind(null, root), );

怎么知道什么时候会调用什么模式呢?

//ensureRootIsScheduled函数

//主要是靠newCallbackPriority优先级来判断,如果是SyncLane那么就是同步模式,反之就是并发模式
if (newCallbackPriority === SyncLane) {
    ....
    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    .....
}else{
    .....
    newCallbackNode = scheduleCallback( schedulerPriorityLevel, // concurrent模式下调用 performConcurrentWorkOnRoot.bind(null, root), );
    .....
}

performConcurrentWorkOnRoot(未完)

我们先看Concurrent吧

function performConcurrentWorkOnRoot(root/*FiberRoot*/, didTimeout/*false*/) {
  ....
  
  //重置
  currentEventTime = NoTimestamp;
  currentEventTransitionLane = NoLanes;

  ....
  
  // 在决定在哪个车道上工作之前,先冲掉任何执行的被动效果。
  // 以防他们(被动效果)安排额外的工作
  const originalCallbackNode = root.callbackNode;
  // flushPassiveEffects()冲洗被动效果
  // 进入flushPassiveEffects()
  const didFlushPassiveEffects = flushPassiveEffects();
  
  ....

flushPassiveEffects

export function flushPassiveEffects(): boolean {
  // 如果 rootWithPendingPassiveEffects 存在,说明
  // 使用了 useEffect 或者有子节点被删除
  if (rootWithPendingPassiveEffects !== null) {
    //缓存根部,因为rootWithPendingPassiveEffects会在flushPassiveEffectsImpl进行清空
    const root = rootWithPendingPassiveEffects;
    // 缓存并清除剩余的车道标志;它必须被重置,因为这个方法可以从不同的地方被调用,而不总是从 commitRoot
    const remainingLanes = pendingPassiveEffectsRemainingLanes;
    pendingPassiveEffectsRemainingLanes = NoLanes;

    //下面几个优先级转换方法我就不一一讲解了,有兴趣可以自己看看
    
    // 将lane转换为事件优先级
    const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
    // 取得最低事件优先级
    const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
    // 保存transition
    const prevTransition = ReactCurrentBatchConfig.transition;
    // 保存当前更新优先级
    const previousPriority = getCurrentUpdatePriority();
    
    
    try {
      ReactCurrentBatchConfig.transition = null;
      // 设置当前更新优先级会获取的最低事件优先级
      setCurrentUpdatePriority(priority);
      // 进入flushPassiveEffectsImpl()
      return flushPassiveEffectsImpl();
    } finally {
      // 恢复优先级
      setCurrentUpdatePriority(previousPriority);
      // 恢复transition
      ReactCurrentBatchConfig.transition = prevTransition;

      // Once passive effects have run for the tree - giving components a
      // chance to retain cache instances they use - release the pooled
      // cache at the root (if there is one)
      releaseRootPooledCache(root, remainingLanes);
    }
  }
  return false;
}

可以看出flushPassiveEffects的作用即是将被动效果全部消除

performConcurrentWorkOnRoot(未完)

回到performConcurrentWorkOnRoot

.....
//这里的didFlushPassiveEffects就是flushPassiveEffects()返回的结果,结果为false,所以不执行
      if (didFlushPassiveEffects) {
        // Something in the passive effect phase may have canceled the current task.
        // Check if the task node for this root was changed.
        if (root.callbackNode !== originalCallbackNode) {
          // The current task was canceled. Exit. We don't need to call
          // `ensureRootIsScheduled` because the check above implies either that
          // there's a new task, or that there's no remaining work on this root.
          return null;
        } else {
          // Current task was not canceled. Continue.
        }
      }
      // 使用存储在根上的字段,确定下一个工作通道的字段
      let lanes = getNextLanes(
        root,
        root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
      );
      // 永远不会执行
      if (lanes === NoLanes) {
        // Defensive coding. This is never expected to happen.
        return null;
      }
      
      
      const shouldTimeSlice =
        !includesBlockingLane(root, lanes) &&
        !includesExpiredLane(root, lanes) &&
        (disableSchedulerTimeoutInWorkLoop || !didTimeout);
        
      //重点来了!!!
      //重点来了!!!
        
      // shouldTimeSlice函数根据lane的优先级,决定是使用并发模式还是同步模式渲染(解决饥饿问题)
      // 走Sync还是Concurrent是根据shouldTimeSlice的结果来判断
      // renderRootConcurrent和renderRootSync会触发不同的渲染逻辑
      
      //我们先假设这里是进入的renderRootConcurrent()
      let exitStatus = shouldTimeSlice
        ? renderRootConcurrent(root, lanes)
        : renderRootSync(root, lanes);

renderRootConcurrent(未完)

function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
  // 保存执行上下文
  const prevExecutionContext = executionContext;
  // 给 executionContext 增加渲染上下文 RenderContext
  executionContext |= RenderContext;
  // 保存现场——将 dispatcher 存入栈
  const prevDispatcher = pushDispatcher();
  
  //如果根或车道发生了变化,扔掉现有的堆栈并准备一个新的,否则退出
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    if (enableUpdaterTracking) {
      if (isDevToolsPresent) {
        const 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);
      }
    }

    workInProgressTransitions = getTransitionsForLanes(root, lanes);
    // 重置 timer
    resetRenderTimer();
    // 准备一个新的栈
    // 作用:创建workInProgress树
    prepareFreshStack(root, lanes);
  }
  
  ....
  
  
  //重点来了!!!
  //重点来了!!!
  
  //循环条件为永真,意味着只有正常执行完workLoopConcurrent才会break,一旦
  //workLoopConcurrent中发生错误,那么将会继续循环,直到成功执行完毕workLoopConcurrent
  do {
    try {
      // 重点
      
      //我们进入workLoopConcurrent看看是什么
      workLoopConcurrent();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);

workLoopConcurrent

function workLoopConcurrent() {
  // 执行工作,直到Scheduler要求放弃
  // shouldYield()就是关键所在
  // shouldYield为控制performUnitOfWork是否继续执行的开关,而performUnitOfWork是由React自己实现的RequestIdleCallback来控制的
  while (workInProgress !== null && !shouldYield()) {
  //进入performUnitOfWork
    performUnitOfWork(workInProgress);
  }
}

performUnitOfWork(未完)

function performUnitOfWork(unitOfWork: Fiber /*workInProgress */): void {
  // 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.
  // unitOfWork.alternate是节点副本和缓存树对象是一样的
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  // export const ProfileMode = /*                    */ 0b000010;
  // export const NoMode = /*                         */ 0b000000;
  // 0b000000转换为boolean值是false

  // 无论如何都会执行beginWork()的
  // 只不过如果是true的情况会记录时间
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    //renderLanes = NoLanes
    // 递
    next = beginWork(current, unitOfWork, renderLanes);
    // 停止profiler计时器
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, renderLanes);
  }

  // dev不管
  resetCurrentDebugFiberInDEV();


  // 收集props
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // 如果没有了新的任务,就执行 "归" 操作
    // 归
    completeUnitOfWork(unitOfWork);
  } else {
    // 反之继续 执行beginWork()
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}

可以看到内部调用了beginWork和completeUnitOfWork两个方法,可以将beginWork看做是递的操作,而complete是归的操作

这里我们暂时返回renderRootConcurrent,后面会继续讲

renderRootConcurrent(完)

  ....
  
  // 重置上下文依赖
  resetContextDependencies();

  // 恢复现场——从栈中 pop dispatcher
  popDispatcher(prevDispatcher);
  // 恢复原来的执行上下文
  executionContext = prevExecutionContext;

  ....

  // 检查该树是否已经完成
  if (workInProgress !== null) {
    //如果仍有剩余的工作
    if (enableSchedulingProfiler) {
      // 继续
      markRenderYielded();
    }
    // RootInProgress表示正在进行中
    return RootInProgress;
  } else {
    // 完成了该树
    if (enableSchedulingProfiler) {
      // 标记 渲染停止
      markRenderStopped();
    }

    // 将此设置为空,表示没有正在进行的渲染
    workInProgressRoot = null;
    workInProgressRootRenderLanes = NoLanes;

    // 返回退出状态
    // 这里将返回退出状态!!!
    return workInProgressRootExitStatus;
  }
}

performConcurrentWorkOnRoot(未完)

  ....
  
  //刚才我们将renderRootConcurrent的内部逻辑梳理了一下,renderRootConcurrent就是内部
  //循环调用workLoopConcurrent,workLoopConcurrent内循环执行performUnitOfWork,
  //而shouldYield()控制着循环次数,当performUnitOfWork执行完毕后workLoopConcurrent会
  //返回一个退出状态给exitStatus,performUnitOfWork内部就是beginWork和compleate的逻辑
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    //现在我们再进入renderRootSync看看
    : renderRootSync(root, lanes);
    
  ....

renderRootSync

可以看出renderRootSync和renderRootConcurrent不能说差不多,只能说一模一样,其中不同的地方就在于那个永真循环调用的是workLoopSync();,而renderRootConcurrent调用的是workLoopConcurrent

function renderRootSync(root: FiberRoot, lanes: Lanes) {
  // 保存当前执行上下文
  const prevExecutionContext = executionContext;
  // 把当前执行上下文替换为render上下文
  executionContext |= RenderContext;
  const prevDispatcher = pushDispatcher();
  
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    if (enableUpdaterTracking) {
      if (isDevToolsPresent) {
        const memoizedUpdaters = root.memoizedUpdaters;
        if (memoizedUpdaters.size > 0) {
          restorePendingUpdaters(root, workInProgressRootRenderLanes);
          memoizedUpdaters.clear();
        }
        movePendingFibersToMemoized(root, lanes);
      }
    }

    // getTransitionsForLanes()获取一下root上的transitions
    workInProgressTransitions = getTransitionsForLanes(root, lanes);
    // 在该函数内开启双缓存树
    prepareFreshStack(root, lanes);
  }

  if (__DEV__) {
    if (enableDebugTracing) {
      logRenderStarted(lanes);
    }
  }

  if (enableSchedulingProfiler) {
    markRenderStarted(lanes);
  }

  do {
    try {
      // 同步工作循环
      
      //不同的地方!!!!
      //不同的地方!!!!
      
      //现在我们进入workLoopSync看看和workLoopConcurrent又有什么不同
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  /**
   * 这里是永真,如果workLoopSync()是抛出错误,那么
   * 将不会执行到break,所以还得再执行一次workLoopSync()
   * 如果workLoopSync()是正常执行完毕,那么就会依次执行break,就
   * 退出永真循环了
   */
  } while (true);
  resetContextDependencies();

  executionContext = prevExecutionContext;
  popDispatcher(prevDispatcher);

  if (workInProgress !== null) {
    // This is a sync render, so we should have finished the whole tree.
    throw new Error(
      'Cannot commit an incomplete root. This error is likely caused by a ' +
        'bug in React. Please file an issue.',
    );
  }

  if (__DEV__) {
    if (enableDebugTracing) {
      logRenderStopped();
    }
  }

  if (enableSchedulingProfiler) {
    markRenderStopped();
  }

  //将此设置为空,表示没有正在进行的渲染
  workInProgressRoot = null;
  workInProgressRootRenderLanes = NoLanes;

  return workInProgressRootExitStatus;
}

workLoopSync

function workLoopSync() {

  //不同之处就在于这个循环条件没有了shouldYield()
  
  while (workInProgress !== null) {
    // performUnitOfWork方法会创建下一个Fiber节点并赋值给workInProgress,
    // 并将workInProgress与已创建的Fiber节点连接起来构成Fiber树
    // performUnitOfWork的工作可以分为两部分:“递”和“归”
    performUnitOfWork(workInProgress);
  }
}

没必要再进入performUnitOfWork内看了吧,所以说不管是workLoopConcurrent还是workLoopSync其内部都是调用了performUnitOfWork,只不过workLoopConcurrent会判断shouldYield(),基于shouldYield()来看是否还没有时间继续执行,如果还有剩余的时间,那么还会执行,而workLoopSync就是一股脑执行完,不管是否还有剩余时间

总结

这篇文章暂时只讲到这里,希望有不懂react为什么会有shouldYield()的同学可以去看看其他文章,这样有利于理解,下一次篇文章,我会继续讲到beginWork和completeWork,和另外一个入口方法performSyncWorkOnRoot

记录学习过程,如有错误欢迎指出