一、为什么需要Fiber架构

React 15的架构存在一个根本性缺陷:更新过程是"同步递归"的,一旦开始就无法中断。当组件树层级深、更新量大时,这个递归过程会独占主线程,导致动画卡顿、响应延迟。Fiber架构的本质,是将这个同步过程"异步化"、"可中断化"。

1.1 Stack Reconciler vs Fiber Reconciler

// React 15 Stack Reconciler(同步递归)
//
// function reconcile(children) {
//   children.forEach(child => {
//     const instance = createInstance(child);  // 同步创建DOM
//     reconcile(child.children);                  // 递归,无法中断
//     appendAllChildren(instance);              // 挂载DOM
//   });
// }
// 问题:10万个组件的更新需要一次性完成
//         ↓
//   V8主线程被占用 ~16ms(1帧)
//   动画60fps → 每帧最多16.67ms
//   更新占用 > 16ms → 掉帧 → 卡顿

// React Fiber(可中断的增量更新)
//
// 工作单元(Fiber节点):
// type Fiber = {
//   // ① 链表指针(替代递归栈)
//   child: Fiber | null,       // 第一个子节点
//   sibling: Fiber | null,     // 下一个兄弟节点
//   return: Fiber | null,      // 父节点
//
//   // ② 工作类型
//   tag: WorkTag,              // 组件类型
//   pendingProps: any,         // 新props
//   memoizedProps: any,        // 旧props
//
//   // ③ 状态
//   memoizedState: any,        // 已提交的state
//   effectTag: SideEffectTag,  // 本次更新的副作用标记
//
//   // ④ 调度
//   expirationTime: ExpirationTime, // 优先级
//   childExpirationTime: ExpirationTime,
// };
//
// // 可中断的关键:每次只处理一个Fiber节点
// function workLoop() {
//   while (nextUnitOfWork !== null) {
//     nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
//   }
// }
// requestIdleCallback(workLoop, { timeRemaining: 5 }); // 空闲时执行
// ↑
// JS主线程空闲时继续执行,否则让出给浏览器调度

二、Fiber树的构建与双缓冲技术

2.1 双缓冲(Double Buffering)

// React Fiber使用"双缓冲"技术管理两棵Fiber树
//
// current Fiber树          workInProgress Fiber树
// ┌──────────────┐        ┌──────────────┐
// │   已渲染的    │        │   正在构建的   │
// │  真实DOM对应   │   ←    │   新更新      │
// └──────────────┘        └──────────────┘
//
// current.alternate === workInProgress
// workInProgress.alternate === current

// 开始更新时:从current克隆出workInProgress
function createWorkInProgress(current, pendingProps) {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    // 首次构建:clone一个Fiber节点
    workInProgress = {
      ...current,
      alternate: current,        // 回指current
      // 重置工作相关字段
      pendingProps: pendingProps,
      memoizedProps: null,
      memoizedState: null,
      effectTag: null,
      child: null,
    };
  } else {
    // 复用:重置状态,保留已有引用
    workInProgress.pendingProps = pendingProps;
    workInProgress.effectTag = null;
    workInProgress.child = null;
  }
  return workInProgress;
}

// 调度流程:
// setState()
//   ↓
// enqueueUpdate()        ← 将update加入updateQueue
//   ↓
// scheduleWork()        ← 请求调度
//   ↓
// requestWork()         ← 决定是否立即reconcile
//   ↓
// performWork()         ← 遍历Fiber树,执行更新
//   ↓
// completeRoot()        ← 提交更新,交换双缓冲
//   ↓
// current = workInProgress(交换引用)

2.2 三种提交阶段(Commit Phases)

// React更新分两个阶段:
// ① Render阶段:可中断(计算差异,构建workInProgress树)
// ② Commit阶段:同步执行(DOM操作、副作用执行)

// Commit阶段不允许中断,原因:
// DOM的多次读取/写入操作必须严格按顺序执行
// 例如:读取offsetHeight → 写入height → 读取offsetHeight
// 如果中间插入其他操作,会产生布局抖动

// Commit阶段又分三个子阶段:
// ┌──────────────────────────────────────────────┐
// │ Phase 1: Before Mutation(DOM操作前)        │
// │ 执行:getSnapshotBeforeUpdate                │
// │ ⚠️ 此阶段不能执行副作用                      │
// └──────────────────────────────────────────────┘
//                    ↓
// ┌──────────────────────────────────────────────┐
// │ Phase 2: Mutation(DOM操作,同步)          │
// │ 执行:所有ref绑定                           │
// │       类组件componentDidMount/Update        │
// │       函数组件的useEffect同步执行           │
// │       DOM的appendChild/removeChild等        │
// └──────────────────────────────────────────────┘
//                    ↓
// ┌──────────────────────────────────────────────┐
// │ Phase 3: Layout(布局后,DOM已更新)        │
// │ 执行:useLayoutEffect的同步回调             │
// │       ref的DOM操作(.focus()/.scroll())   │
// └──────────────────────────────────────────────┘

// ⚠️ 为什么useEffect在mutation阶段之后执行?
// 因为useEffect是异步的,不阻塞DOM更新
// 浏览器可以在DOM更新后先显示,再执行副作用

三、优先级调度机制

3.1 优先级层级

// React Fiber的5级优先级(从高到低)
//
// SyncLane             ← 最高:setState同步调用、DOM事件
// InputContinuousLane  ← 连续输入:scroll、drag事件
// DefaultLane          ← 普通:fetch结果、transition
// TransitionLane       ← 过渡:useTransition标记的更新
// IdleLane            ← 最低:预渲染、低优先级工作

// Lane模型(位运算实现)
// 用一个31位整数表示优先级
// 每个Lane对应一个bit位
//
// 0b0000000000000000000000000000001  ← SyncLane (1)
// 0b0000000000000000000000000000010  ← InputContinuousLane (2)
// 0b0000000000000000000000000000100  ← DefaultLane (4)
// 0b0000000000000000000000000001000  ← TransitionLane (8)
//
// 运算优势:
// - 判断是否有更高优先级任务:lanes & ~higherPriorityLanes
// - 合并多个Lane:lanes | newLane
// - 清除某个Lane:lanes & ~targetLane

// 优先级决定expirationTime
function computeExpirationTime(currentTime, priorityLevel) {
  switch (priorityLevel) {
    case SyncLane: return currentTime; // 立即(sync)
    case InputContinuousLane: return currentTime - 3;
    case DefaultLane: return currentTime - 5;
    case TransitionLane: return currentTime - 10;
    case IdleLane: return currentTime + 100;
  }
}

// 过期判断:
// currentTime >= expirationTime → 必须立即执行(sync)
// currentTime < expirationTime → 可以被低优先级抢占

3.2 useTransition的实现原理

// useTransition让某些更新可被中断
import { useTransition } from 'react';

function SearchResults() {
  const [isPending, startTransition] = useTransition();
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  function handleSearch(e) {
    const value = e.target.value;
    setQuery(value);

    // startTransition内的更新优先级降低
    // 即使渲染慢,也不会阻塞用户输入
    startTransition(() => {
      setResults(searchInLargeDataset(value));
    });
  }

  return (
    <div>
      <input value={query} onChange={handleSearch} />
      {isPending ? <Spinner /> : <Results data={results} />}
    </div>
  );
}

// 内部实现:
// startTransition(callback) {
//   callback();  // callback内所有setState → TransitionLane
// }

// 效果:
// 输入框更新 → SyncLane(立即,高优先级)→ 输入立即响应
// 结果列表更新 → TransitionLane(可中断)→ 可以被输入抢占

四、Diff算法的精准分析

4.1 三条核心规则

// React Diff的三条规则(决定了调和算法复杂度)
//
// 规则①:不同类型的元素产生不同的树
// <div> → <span>  → 卸载div,替换为span子树
// 规则②:通过key优化同层级列表的移动
// 规则③:同一类型的元素只对比属性

// 规则①:元素类型变化 → 完整替换
// <div key="a">A</div>
//       ↓
计划变成 → <section key="a">B</section>
// 实际:卸载整个div树(A被销毁),挂载新section树(B被创建)

// 规则③:同类型元素 → 只更新变化的属性
// <div className="old" style={{color:"red"}}>
//       ↓  →  只更新className和style,不重新创建DOM节点
// <div className="new" style={{color:"blue"}}>

// 规则②:列表diff的核心 —— key的作用
//
// 无key(错误):
// [A, B, C] → [A, B, C](删除中间的B)
// 操作: 卸载C → 卸载A → 创建C → 创建A  ← O(N²)
// 有key:
// [A, B, C] → [A, C](删除中间的B,key="A","B","C")
// 操作: 只卸载B  ← O(N)

// ⚠️ 错误使用key导致性能倒退
// ❌ 用index作key(在列表重排时)
// items.map((item, index) => <Item key={index} />)
// 数组[1,2,3]→[3,2,1]时:所有Item都被重建

// ✅ 用稳定ID作key
// items.map(item => <Item key={item.id} />)
// 数组[1,2,3]→[3,2,1]时:复用已有节点,只需移动

五、Concurrent Mode的调度本质

// Concurrent Mode的并发调度器(基于Fiber的Scheduler)
//
// 调度器维护一个"小顶堆"(Min Heap)存储待执行任务:
// 优先级最高的任务在堆顶
//
//       [Task: exp=5]           ← 当前最高优先级
//        /         \
//  [exp=8]         [exp=12]
//
// requestHostCallback 调度循环:
// while (workInProgress !== null) {
//   if (getCurrentTime() >= deadline) {
//     // 时间片用完,主动让出主线程
//     requestYield();  // → requestIdleCallback在下帧继续
//     return;          // 中断!其他任务可以执行
//   }
//   workInProgress = performUnitOfWork(workInProgress);
// }
//
// 任务可抢占的时机:
// 1. 子树遍历完一个节点
// 2. 遇到SuspenseBoundary(挂起)
// 3. 时间片耗尽

// Suspense的调度逻辑
<Suspense fallback={<Spinner />}>
  <HeavyComponent />   ← 需要异步加载数据
</Suspense>

// HeavyComponent内部:
// if (!data) {
//   throw promise;  // 抛出Promise → Suspense捕获 → 显示fallback
// }
// ⚠️ 抛出Promise后,整个Fiber子树的工作被挂起
// Suspense外的更新不受影响(不受阻塞)