react中render阶段做了什么

首先说明一个概念:

render阶段对应的是Reconciler(协调器),

commit阶段对应的的是Renderer(渲染器)

render阶段开始于performSyncWorkOnRoot或performConcurrentWorkOnRoot方法的调用。这取决于本次更新是同步更新还是异步更新。

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

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

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

"递"阶段

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

该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。

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

也会做一些标记,这个标记主要和元素的位置有关系。

javascript 复制代码
// DOM需要插入到页面中
export const Placement = /*                */ 0b00000000000010;
// DOM需要更新
export const Update = /*                   */ 0b00000000000100;
// DOM需要插入到页面中并更新
export const PlacementAndUpdate = /*       */ 0b00000000000110;
// DOM需要删除
export const Deletion = /*                 */ 0b00000000001000;

"归"阶段:

  • 为Fiber节点生成对应的DOM节点
  • 将子孙DOM节点插入刚生成的DOM节点中
javascript 复制代码
// mount的情况

// ...省略服务端渲染相关逻辑

const currentHostContext = getHostContext();
// 为fiber创建对应DOM节点
const instance = createInstance(
  type,
  newProps,
  rootContainerInstance,
  currentHostContext,
  workInProgress
);
// 将子孙DOM节点插入刚生成的DOM节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM节点赋值给fiber.stateNode
workInProgress.stateNode = instance;

// 与update逻辑中的updateHostComponent类似的处理props的过程
if (
  finalizeInitialChildren(
    instance,
    type,
    newProps,
    rootContainerInstance,
    currentHostContext
  )
) {
  markUpdate(workInProgress);
}

那么commit阶段是如何通过一次插入DOM操作(对应一个Placement effectTag)将整棵DOM树插入页面的呢?

原因就在于completeWork中的appendAllChildren方法。

由于completeWork属于"归"阶段调用的函数,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下。那么当"归"到rootFiber时,我们已经有一个构建好的离屏DOM树。
EffectList:

至此render阶段的绝大部分工作就完成了。

还有一个问题:作为DOM操作的依据,commit阶段需要找到所有有effectTag的Fiber节点并依次执行effectTag对应操作。难道需要在commit阶段再遍历一次Fiber树寻找effectTag !== null的Fiber节点么?

这显然是很低效的。

为了解决这个问题,在completeWork的上层函数completeUnitOfWork中,每个执行完completeWork且存在effectTag的Fiber节点会被保存在一条被称为effectList的单向链表中。

effectList中第一个Fiber节点保存在fiber.firstEffect,最后一个元素保存在fiber.lastEffect。

类似appendAllChildren,在"归"阶段,所有有effectTag的Fiber节点都会被追加在effectList中,最终形成一条以rootFiber.firstEffect为起点的单向链表。

javascript 复制代码
                      nextEffect         nextEffect
rootFiber.firstEffect -----------> fiber -----------> fiber

这样,在commit阶段只需要遍历effectList就能执行所有effect了。

相关推荐
不会写DN3 小时前
Gin 日志体系详解
前端·javascript·gin
冬夜戏雪3 小时前
实习面经记录(十)
java·前端·javascript
爱学习的程序媛4 小时前
【Web前端】JavaScript设计模式全解析
前端·javascript·设计模式·web
老黑5 小时前
开源工具 AIDA:给 AI 辅助开发加一个数据采集层,让 AI 从错误中自动学习(Glama 3A 认证)
前端·react.js·ai·nodejs·cursor·vibe coding·claude code
薛先生_0995 小时前
js学习语法第一天
开发语言·javascript·学习
苦瓜小生5 小时前
【前端】|【js手撕】经典高频面试题:手写实现function.call、apply、bind
java·前端·javascript
和沐阳学逆向5 小时前
我现在怎么用 CC Switch 管中转站,顺手拿 Codex 举个例子
开发语言·javascript·ecmascript
kgduu8 小时前
js之客户端存储
javascript·数据库·oracle
四千岁8 小时前
2026 最新版:WSL + Ubuntu 全栈开发环境,一篇搞定!
javascript·node.js
竹林8188 小时前
从“连接失败”到丝滑登录:我用 ethers.js 连接 MetaMask 的完整踩坑实录
前端·javascript