React 源码解析(Fiber & Reconciler)
源码核心模块
1. Fiber 数据结构
type Fiber = {
tag: WorkTag // 节点类型(FunctionComponent/ClassComponent/HostComponent...)
key: null | string
type: any // 对应 createElement 的第一个参数
stateNode: any // DOM 节点 或 class 实例
return: Fiber | null // 父节点
child: Fiber | null // 第一个子节点
sibling: Fiber | null // 下一个兄弟节点
pendingProps: any // 本次渲染的 props
memoizedProps: any // 上次渲染的 props
memoizedState: any // hooks 链表的头节点
flags: Flags // 副作用标记(Placement/Update/Deletion)
subtreeFlags: Flags
updateQueue: any // 状态更新队列
}
2. 双缓冲机制
- React 同时维护两棵 Fiber 树:current tree(当前屏幕)和 workInProgress tree(构建中)
- 渲染完成后
root.current = workInProgress,两棵树交替复用 - 好处:构建过程可中断,不影响当前显示内容
3. Reconcile 阶段(beginWork & completeWork)
深度优先遍历 Fiber 树
↓
beginWork:根据 type 处理节点(diff children,打 flags)
↓
若有子节点 → 递归处理子节点
若无子节点 → completeWork:创建/更新 DOM,收集 subtreeFlags
↓
返回父节点继续 completeWork
Diff 策略
- 单节点 Diff:key 和 type 都相同 → 复用;否则销毁重建
- 多节点 Diff:两轮遍历
- 第一轮:按顺序对比,遇到 key 不同则停止
- 第二轮:将剩余旧节点放入 Map,用 key 查找可复用节点
- 最长递增子序列优化移动操作(React 18)
4. Commit 阶段(三个子阶段)
| 子阶段 | 工作内容 |
|---|---|
| BeforeMutation | 读取 DOM 快照,调用 getSnapshotBeforeUpdate |
| Mutation | 操作真实 DOM(增/改/删),调用 useLayoutEffect 清理 |
| Layout | 调用 useLayoutEffect 回调,更新 ref |
useEffect在 commit 后异步调度,不阻塞浏览器绘制useLayoutEffect在 Layout 阶段同步执行,会阻塞绘制
5. Hooks 实现原理
Fiber.memoizedState → Hook₁ → Hook₂ → Hook₃ (链表)
↓
{ memoizedState, queue, next }
- mount 阶段:每个
useState在链表末尾插入新 Hook 节点 - update 阶段:按顺序取链表节点,计算新状态
- 为什么不能条件渲染 Hooks:顺序错位会取到错误的 Hook 节点
6. 调度器(Scheduler)
- 基于
MessageChannel实现任务切片(非 requestAnimationFrame) - 每帧预算 5ms,超出则中断,让出主线程
- 优先级队列:ImmediatePriority > UserBlockingPriority > NormalPriority > IdlePriority
lane模型(React 18):用位运算表示优先级,支持批量处理多个优先级
7. 高频面试题
- 为什么要用 MessageChannel 而非 setTimeout? setTimeout 最小延迟 ~4ms,MessageChannel 延迟更低
- useEffect 清理函数什么时候执行? 下次 effect 执行前 或 组件 unmount 时
- Concurrent Mode 如何保证状态一致性? 同一优先级的更新批量处理,低优先级更新被高优先级打断后重新计算