React原理一
react 在 16版本中引入fiber的概念,但是其中相关特性例如任务的可中断实际上我们开发者并不能直接控制,直到Concurrent Mode模式(17版本提供,但是当时相当于是不稳定模式,主要是为了18版本做准备)的出现,提供了并发特性,才能高优先级打断低优先级;
先了解一下fiber的结构,因为字段非常多我们记一些非常重要的字段
- type:节点的类型(如 div、span,或者是组件的构造函数)。
- stateNode:表示当前 Fiber 节点所对应的组件实例或 DOM 元素。
- return:指向当前节点的父节点,用来建立 Fiber 树的层级关系。
- child、sibling:分别指向当前节点的第一个子节点和下一个兄弟节点。
- updateQueue:包含此节点的更新任务队列(如 setState 等)。
- alternate:表示当前节点和 WorkInProgress 树中的“备份”节点,用于实现双缓存(双缓冲)。这使得 React 能够同时存在两棵树(current 和 WIP),并根据需要切换。
- effectTag:表示需要执行的副作用(例如插入、更新或删除)。
- deletions:记录要删除的子节点。
- 副作用链表(effectList、firsteffect、nexteffect…)
React更新工作流程
-
触发更新:当调用 setState 或其他更新方法时,React 会创建一个更新对象,将其添加到对应 Fiber 节点的 updateQueue 中。这个时候 current fiber 依然表示当前渲染的 UI。
- React 会把来自函数的更新包装成一个更新对象 然后添加到 对应的fier节点的更新队列 Update Queue;此时的fiber我们称它为current fiber; 每个current fiber节点都有自己的更新队列
- 调度更新:React 会根据更新的优先级(例如,用户交互的事件优先级较高),将根节点(Root Fiber)标记为待更新,并将其添加到全局调度器(Scheduler)中。调度器根据优先级和空闲时间来决定什么时候执行这个更新。
-
协调阶段 (Reconciliation)
流程:在 协调阶段,React 开始对比当前的 Fiber 树(current fiber)和新的 React 元素树(执行函数组件生成),然后构建出 WorkInProgress Fiber Tree。这个过程涉及对比(Diffing)、更新、插入和删除节点。React 采用 深度优先遍历来遍历树,从根节点开始,逐个访问每个子节点(包括子节点的子节点)。遍历过程中会执行 beginWork,并在对比完成后返回并执行 completeWork。- 什么是深度优先
- 遍历顺序:根节点 → 子节点 → 子节点的子节点 → … → 直到最深的叶子节点。(当没有子节点并子节点执行完 会寻找兄弟节点,如果兄弟节点也没有了 执行返回操作)
- 返回顺序:当到达叶子节点后,回溯到父节点,继续访问兄弟节点,直到树的遍历完成。
-
构建 WorkInProgress Tree
- React 不会改变current fiber, 他会在对比的过程中去构建新的fiber 树,这里我们称之为WIP tree(WorkInProgress Tree),这里需要注意的是WIP tree的构建是 边对比边构建,这里和上面的深度遍历时同时进行的并且按遍历的顺序执行beginWork
- beginWork 在 WIP tree中遍历树的每个节点,决定是否需要更新、删除或创建新的 Fiber 节点。
- 被复用的节点WIP tree和current fiber会存在双向指向 通过 alternate 字段,新建的节点WIP tree的alternate 指向null
- 构建过程中会将有副作用的节点通过副作用链表指针串起来
- 协调子节点的过程中:
- 直接复用:新的react 元素和 current fiber 子节点完全匹配,updateQueue 为空,且接收的 props 也完全相同, WIP tree父节点直接指向 current fiber子节点;构建的过程都跳过直接赋值;
- 复用并更新:新的 React 元素的 key 和 type 与一个旧 Fiber 节点匹配,但是有待更新任务或者接收了新的props,React 会克隆这个旧的 current fiber 节点来创建一个新的 WIP Fiber 节点,并建立 alternate 链接。然后,它会为这个新 WIP 节点打上相应的“更新”副作用标记
- 新建: 新的 React 元素的 key 或 type 与任何旧的子 Fiber 节点都对不上;React 会在WIP Fiber创建一个全新的 Fiber 节点,并为其打上“插入”的副作用标记,alternate 指向null
- 删除: 一个旧的子 Fiber 节点没有出现在新的 React 元素数组中,React 不会为这个旧节点创建新的 WIP 节点,而是会给它打上“删除”的标签,并将其记录到父 Fiber 的 deletions 数组中
- React 不会改变current fiber, 他会在对比的过程中去构建新的fiber 树,这里我们称之为WIP tree(WorkInProgress Tree),这里需要注意的是WIP tree的构建是 边对比边构建,这里和上面的深度遍历时同时进行的并且按遍历的顺序执行beginWork
-
完成节点处理(completeWork,这里还是在构建WIP tree的过程中)
- 当一个 Fiber 节点的所有子节点都处理完毕后,会进入“完成”阶段,会调用completeWork函数,这里就是上文深度优先的返回过程
- completeWork具体做了什么:
- React 会为宿主组件创建真实的 DOM 节点(但不会插入到文档中),并处理其属性(如 className, style),
- 如果 beginWork 标记了需要删除、更新或添加的节点,completeWork 会在此阶段执行 DOM 的插入、删除或更新操作
- 在 并发模式 下,completeWork 还会负责处理高优先级的渲染任务和副作用
- completeWork具体做了什么:
- 当一个 Fiber 节点的所有子节点都处理完毕后,会进入“完成”阶段,会调用completeWork函数,这里就是上文深度优先的返回过程
-
直到返回遍历完成,此时就有两颗树:
- Current Tree:代表当前UI,保持稳定。
- WorkInProgress Tree (WIP Tree):代表“下一个UI”,正在被逐步构建。它是由一堆 alternate 指针链接起来的、部分更新后的 Fiber 节点副本
- 什么是深度优先
-
提交阶段
- 这个阶段是同步的、不可中断的。React 会一口气将上一阶段计算出的所有变更应用到真实的 DOM 上,用户会看到屏幕更新。
- 源码中有一段
root.current = finishedWork
这里表示将 WorkInProgress Tree 替换 current fiber - 替换后,React 会遍历副作用链表,分三个子阶段执行所有副作用
- Before mutation:调用 getSnapshotBeforeUpdate 生命周期等。
- Mutation:执行真实的 DOM 操作。插入新节点、更新节点属性、删除旧节点。
- Layout:DOM 已经更新完毕。在这个阶段同步执行的代码(如 useLayoutEffect)可以安全地访问到最新的 DOM
- 清理与调度Effects:
- 在提交阶段之后,React 会调度被动 Effects(Passive Effects) 的执行,即 useEffect 钩子。这些 effect 会在浏览器绘制完成后异步执行,不会阻塞浏览器的渲染。