第三章render阶段:3.1 流程概览

429 阅读3分钟

本专栏致力于分析热门项目,如果本文对你有帮助的话,欢迎点赞或关注。

render阶段开始于performSyncWorkOnRoot或performConcurrentWorkOnRoot方法的调用。这取决于本次更新是同步更新还是异步更新。他们唯一的区别是是否调用shouldYield。如果当前浏览器帧没有剩余时间,shouldYield会中止循环,直到浏览器有空闲时间后再继续遍历。workInProgress代表当前已创建的workInProgress fiber。performUnitOfWork方法会创建下一个Fiber节点并赋值给workInProgress,并将workInProgress与已创建的Fiber节点连接起来构成Fiber树。

// performSyncWorkOnRoot会调用该方法
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

// performConcurrentWorkOnRoot会调用该方法
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

由于render函数中的逻辑比较复杂,为了方便大家理解,我们可以将render函数分为两个阶段,分别是渲染阶段和提交阶段,其中渲染阶段又可以分为beginWork和completeWork两个阶段,而提交阶段对应着commitWork。如今在Fiber架构下,变成了 虚拟DOM->Fiber树->真实DOM。相当于多了一层Fiber。那么虚拟转化为Fiber树,可以认为就是我们前面说的beginWork,Fiber树转化真实DOM就是completeWork,转化为真实DOM之后挂载到页面,就是commitWork。

为什么要这样划分呢?其实很好理解。Fiber本身包括了虚拟DOM在内的很多信息,而这些丰富的信息能够支持Fiber在执行任务的过程中被中断和恢复。这里说的beginWork和completeWork其实就是就是在执行Fiber相关任务————虚拟转化为Fiber,Fiber转化为真实DOM。但是Fiber转化为真实DOM后挂载到页面的这个过程是不可以中断的。Fiber内部怎么运行都可以随便来,但是涉及到和页面真实发生关系的时候是不可以中断的。这也就是区分为渲染阶段和提交阶段的原因。也就是说渲染阶段可以中断恢复,提交阶段不可以。而beginWork和completeWork的划分更多的只是从程序逻辑的角度进行拆分,它们都属于渲染阶段。

我们知道Fiber Reconciler是从Stack Reconciler重构而来,通过遍历的方式实现可中断的递归,performUnitOfWork的工作可以分为两部分:“递”和“归”。

“递”阶段:

首先从rootFiber开始向下深度优先遍历。为遍历到的每个Fiber节点调用beginWork 方法。该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。

“归”阶段:

在“归”阶段会调用completeWork处理Fiber节点。当某个Fiber节点执行完completeWork,如果其存在兄弟Fiber节点(即fiber.sibling !== null),会进入其兄弟Fiber的“递”阶段。如果不存在兄弟Fiber,会进入父级Fiber的“归”阶段。“递”和“归”阶段会交错执行直到“归”到rootFiber。至此,render阶段的工作就结束了。

栗子:

function App() {
  return (
    <div>
      i am
      <span>KaSong</span>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

对应的Fiber树结构:

截屏2024-10-15 13.41.32.png

render阶段会依次执行:

 rootFiber beginWork
2. App Fiber beginWork
3. div Fiber beginWork
4. "i am" Fiber beginWork
5. "i am" Fiber completeWork
6. span Fiber beginWork
7. span Fiber completeWork
8. div Fiber completeWork
9. App Fiber completeWork
10. rootFiber completeWork

之所以没有 “KaSong” Fiber 的 beginWork/completeWork,是因为作为一种性能优化手段,针对只有单一文本子节点的Fiber,React会特殊处理。

如果将performUnitOfWork转化为递归版本,大体代码如下:

function performUnitOfWork(fiber) {
  // 执行beginWork

  if (fiber.child) {
    performUnitOfWork(fiber.child);
  }

  // 执行completeWork

  if (fiber.sibling) {
    performUnitOfWork(fiber.sibling);
  }
}

参考链接

关于作者

作者:Wandra

内容:算法 | 趋势 |源码|Vue | React | CSS | Typescript | Webpack | Vite | GithubAction | GraphQL | Uniqpp。

专栏:欢迎关注🌹

本专栏致力于分析热门项目,如果本文对你有帮助的话,欢迎点赞或关注。