React Fiber 架构与调和算法
React 16 引入 Fiber 架构,彻底重写了调和(Reconciliation)引擎,实现了可中断渲染和并发模式。
为什么需要 Fiber
React 15 的 Stack Reconciler 是递归同步的,一旦开始就无法中断:
Stack Reconciler(React 15):
renderComponent(App)
renderComponent(Header)
renderComponent(Nav) ← 深度递归,无法暂停
...
问题:大型组件树更新时,JS 线程被占用超过 16ms(60fps 的帧预算),
导致动画卡顿、用户输入无响应。Fiber 的解决方案:将渲染工作切分为小单元,可以暂停、恢复、丢弃。
Fiber 节点结构
ts
// 每个 React 元素对应一个 Fiber 节点
interface FiberNode {
// 节点类型
type: string | Function | null
key: string | null
// 链表结构(替代递归调用栈)
child: FiberNode | null // 第一个子节点
sibling: FiberNode | null // 下一个兄弟节点
return: FiberNode | null // 父节点
// 状态
pendingProps: any
memoizedProps: any
memoizedState: any // Hook 链表的头节点
// 副作用
flags: Flags // 需要执行的操作(插入/更新/删除)
subtreeFlags: Flags
updateQueue: UpdateQueue // 待处理的更新队列
// 双缓冲
alternate: FiberNode | null // 对应的另一棵树的节点
}双缓冲机制
React 维护两棵 Fiber 树:
current tree(当前显示的) workInProgress tree(正在构建的)
┌──────┐ ┌──────┐
│ App │◄──── alternate ─────►│ App │
└──┬───┘ └──┬───┘
│ │
┌──▼───┐ ┌──▼───┐
│Header│◄──── alternate ─────►│Header│
└──────┘ └──────┘
渲染完成后,workInProgress 变成新的 current渲染两个阶段
Render 阶段(可中断)
beginWork(向下):
App → Header → Nav → Button
↓
completeWork(向上):
Button → Nav → Header → App
每个节点的 beginWork:
1. 根据 type 创建/更新 Fiber 节点
2. 对比新旧 props,标记副作用(flags)
3. 返回第一个子节点(继续向下)
每个节点的 completeWork:
1. 创建/更新 DOM 节点
2. 收集子树的副作用到父节点(subtreeFlags)Commit 阶段(同步,不可中断)
三个子阶段:
1. BeforeMutation:调用 getSnapshotBeforeUpdate
2. Mutation:执行 DOM 操作(插入/更新/删除)
3. Layout:调用 componentDidMount/Update,useLayoutEffectDiff 算法
React 的 Diff 基于三个假设(启发式算法,O(n) 复杂度):
- 不同类型的元素产生不同的树
- 同级元素通过
key标识 - 开发者可以通过
key提示哪些子元素是稳定的
jsx
// 规则一:类型不同 → 销毁重建
// 旧:<div><Counter /></div>
// 新:<span><Counter /></span>
// Counter 会被卸载再重新挂载(state 丢失)
// 规则二:key 的重要性
// ❌ 没有 key:列表重排时,React 逐个对比,可能错误复用
function BadList({ items }) {
return items.map(item => <Item value={item.value} />)
}
// ✅ 有 key:React 通过 key 识别节点身份
function GoodList({ items }) {
return items.map(item => <Item key={item.id} value={item.value} />)
}
// ❌ 不要用 index 作为 key(列表有增删时)
items.map((item, index) => <Item key={index} />)
// 删除第一项后,所有 key 都变了,React 会错误地复用组件
// ✅ 用稳定的唯一 ID
items.map(item => <Item key={item.id} />)并发特性(React 18)
jsx
import { useTransition, useDeferredValue, startTransition } from 'react'
// useTransition:标记低优先级更新
function SearchPage() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [isPending, startTransition] = useTransition()
function handleChange(e) {
// 高优先级:立即更新输入框
setQuery(e.target.value)
// 低优先级:搜索结果可以延迟
startTransition(() => {
setResults(search(e.target.value))
})
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending ? <Spinner /> : <Results data={results} />}
</>
)
}
// useDeferredValue:延迟某个值的更新
function App() {
const [text, setText] = useState('')
const deferredText = useDeferredValue(text) // 延迟跟随 text
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
{/* SlowList 使用 deferredText,不会阻塞输入 */}
<SlowList text={deferredText} />
</>
)
}Scheduler 调度器
优先级(从高到低):
ImmediatePriority → 同步,立即执行(如用户输入)
UserBlockingPriority → 250ms 内(如点击、hover)
NormalPriority → 5s 内(如网络请求回调)
LowPriority → 10s 内(如数据预加载)
IdlePriority → 空闲时执行(如日志上报)
调度机制:
- 使用 MessageChannel 实现异步调度(比 setTimeout 更精确)
- 每帧预留 5ms 给 React 工作
- 超时则暂停,下一帧继续(时间切片)总结
- Fiber 将渲染拆分为可中断的小单元,解决了大树更新卡顿问题
- 双缓冲:current 树显示,workInProgress 树在后台构建
- Render 阶段可中断,Commit 阶段同步不可中断
- Diff 算法是启发式 O(n),
key是正确 Diff 的关键 - React 18 并发特性(useTransition/useDeferredValue)基于 Fiber 优先级调度