React 源码系列:了解一下 renderRootConcurrent

1,197 阅读2分钟

「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

通过前两篇文章我们了解了 scheduleCallback 是怎么实现的,现在回到上一层,看看遗落没有展开的内容。在 ensureRootIsScheduled 中,如果 getHighestPriorityLane 调用的返回值不是 SyncLane的时候,则有以下代码:

 newCallbackNode = scheduleCallback(
     schedulerPriorityLevel,
     performConcurrentWorkOnRoot.bind(null, root),
 )
 root.callbackNode = newCallbackNode

现在我们知道了 scheduleCallback 进行对第二个参数进行调度,performConcurrentWorkOnRoot.bind(null, root) 最终会在某个时机被执行,那么 performConcurrentWorkOnRoot 函数又做了什么关键的操作?

函数内执行的关键函数

忽略与出错时进行恢复操作的代码,关键函数如下:

  • flushPassiveEffects:执行某些副作用
  • ensureRootIsScheduled:形成递归执行
  • renderRootConcurrent:与 renderRootSync 只能两选一,如果执行的是renderRootConcurrent,最后会执行 finishConcurrentRender
  • renderRootSync: 与 renderRootConcurrent 二选一
  • finishConcurrentRender:执行完成并发渲染的后置操作(我估计是这样的)

renderRootConcurrent

分析一下 renderRootConcurrent 函数。

函数定义如下:

 function renderRootConcurrent (root: FiberRoot, lanes: Lanes) {
   const prevExecutionContext = executionContext
   executionContext |= RenderContext
   const prevDispatcher = pushDispatcher()
 
   if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
     resetRenderTimer()
     prepareFreshStack(root, lanes)
   }
 
   do {
     try {
       workLoopConcurrent()
       break
     } catch (thrownValue) {
       handleError(root, thrownValue)
     }
   } while (true)
   resetContextDependencies()
 
   popDispatcher(prevDispatcher)
   executionContext = prevExecutionContext
 
   if (workInProgress !== null) {
     return RootIncomplete
   } else {
 
     workInProgressRoot = null
     workInProgressRootRenderLanes = NoLanes
     return workInProgressRootExitStatus
   }
 }

从这个函数的核心实现(已省略调试和 profiler 等不重要的代码)中我们了解到,实现的逻辑主要如下

  1. 记录原来的 executionContext, 然后给 executionContext 增加渲染上下文 RenderContext
  2. 保存现场——将 dispatcher 存入栈
  3. 如果有问题则重置 timer,并 prepareFreshStack
  4. 执行一看就很关键的 workLoopConcurrent
  5. 重置上下文依赖,即调用 resetContextDependencies
  6. 回复现场——从栈中 pop dispatcher
  7. 恢复原来的 executionContext
  8. 如果 workInProgress 不为 null,然后返回 RootIncomplete 状态,否则重置 workInProgressRoot 和 workInProgressRootRenderLanes,返回 workInProgressRootExitStatus 状态

workLoopConcurrent

作用就一句话—— 持续调用 performUnitOfWork 直到 workInProgress 为空或者 shouldYield 函数返回 false。

shouldYield 就是 scheduler 库里的 unstable_shouldYield 函数,该函数的作用是当处于需要中断 react的渲染,将控制交还给“主线程”的场景时返回 true,具体来说就是当前时间距离全局变量 startTime 的间隔小于帧最小间隔(约为 5ms)的时候返回 true。

performUnitOfWork

主要是执行 beginWork 逻辑,然后将 workInProgress.pedingProps 的内容复制给 workInProgress.memoizedProps, 最后 ReactCurrentOwner.current 重置为 null。

代码核心实现如下:

function performUnitOfWork (unitOfWork: Fiber): void {
  const current = unitOfWork.alternate
  setCurrentDebugFiberInDEV(unitOfWork)
  let next = beginWork(current, unitOfWork, subtreeRenderLanes)
  unitOfWork.memoizedProps = unitOfWork.pendingProps
  if (next === null) {
    completeUnitOfWork(unitOfWork)
  } else {
    workInProgress = next
  }

  ReactCurrentOwner.current = null
}

下期预告

直到现在,我们仍没看到 react 是如何实现 dom 的更新的,dom diff 又是如何进行的,而这部分逻辑隐藏在 beginWork 之下,明天我们再来瞅一瞅 beginWork 的实现。