TOP

React 中断与恢复

React 的并发渲染支持可中断和可恢复的特性,这是实现时间分片和优先级调度的基础。

当时间片用完或有更高优先级的任务时,React 会中断当前工作,让出控制权给浏览器。恢复时,React 可以从中断的地方精确继续,不会丢失任何进度。

核心机制

React 通过以下方式实现中断与恢复:

  1. 循环遍历:使用循环而非递归遍历 Fiber 树,支持随时中断
  2. 状态保存:通过 workInProgress 指针和 Fiber 节点本身保存进度
  3. Lane 冲突判断:根据 lane 是否冲突决定是恢复 WIP 还是重新构造

中断机制

React 使用循环处理工作单元,每次循环检查是否需要中断:

function workLoopConcurrent() {
  // 循环处理,直到完成或被中断
  while (workInProgress !== null && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

function shouldYield(): boolean {
  if (deadline === null) {
    return false;  // 同步模式,不中断
  }
  
  // 检查是否还有时间片
  return deadline.timeRemaining() <= timeHeuristicForUnitOfWork;
}

中断时机:

状态保存

中断时,React 的状态保存在两个地方:

1. workInProgress 指针

保存在 FiberRoot 上,指向当前正在处理的节点:

interface FiberRoot {
  current: Fiber;              // current 树的根节点
  workInProgress: Fiber | null; // 指向当前正在处理的节点
  workInProgressRootRenderLanes: Lanes;  // 当前 WIP 的 lanes
}

2. Fiber 节点本身

每个 Fiber 节点保存了自身的状态:

interface Fiber {
  // 状态数据
  memoizedProps: any;     // 已处理的 props
  memoizedState: any;     // 已处理的状态(Hooks)
  updateQueue: UpdateQueue | null;  // 更新队列
  
  // 副作用标记
  flags: Flags;           // 副作用标记(Placement, Update, Deletion等)
  subtreeFlags: Flags;    // 子树副作用标记
  
  // 树结构
  child: Fiber | null;    // 第一个子节点
  sibling: Fiber | null;  // 下一个兄弟节点
  return: Fiber | null;   // 父节点
  
  // 连接关系
  alternate: Fiber | null;  // 另一个树中对应的节点
}

关键点:

恢复机制的关键:Lane 冲突判断

核心问题:高优先级任务插入时,是恢复旧的 WIP 还是重新构造?

答案:取决于 lane 是否冲突

function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
  // 检查是否需要重新开始
  if (workInProgressRoot !== root || 
      workInProgressRootRenderLanes !== lanes) {
    
    // 关键判断:新 lanes 是否与当前 render lanes 冲突
    if (includesSomeLane(workInProgressRootRenderLanes, lanes)) {
      // Lane 冲突:当前 WIP 作废,必须重新构造
      prepareFreshStack(root, lanes);
    } else {
      // Lane 不冲突:可以暂时挂起,等高优先级完成后恢复
      // workInProgress 保留
    }
  }
  
  // 继续工作循环
  workLoopConcurrent();
}

lane 冲突的含义:

三种恢复场景

场景 1:时间片中断(相同 lanes)

中断原因:时间片用完,让出控制权给浏览器

关键点:没有新的更新插入,workInProgress 保留

时间线:
1. 任务正在渲染
   ├─ root.workInProgress = div
   ├─ root.workInProgressRootRenderLanes = TransitionLane
   ├─ App 已处理完成 ✅
   ├─ div 部分处理 ✅
   └─ span 未处理 ❌
  ↓
2. 时间片用完,中断
   ├─ workInProgress 指针保留 ✅
   └─ 让出控制权给浏览器
  ↓
3. 恢复时(相同 lanes)
   ├─ workInProgress 仍然有效 ✅
   └─ 从 workInProgress 继续处理

处理方式:

场景 2:Lane 不冲突(可恢复)

中断原因:高优先级任务插入,但不影响当前 render lanes

关键点:高优先级更新不命中当前 render lanes,WIP 可以保留

时间线:
1. 低优先级任务正在渲染
   ├─ root.workInProgress = div
   ├─ root.workInProgressRootRenderLanes = TransitionLane
   ├─ 正在处理 ComponentA
   └─ current 树:<App><ComponentA /><ComponentB /></App>
  ↓
2. 高优先级任务插入(用户输入)
   ├─ 新增 update:InputContinuousLane 在 ComponentB
   ├─ 检查:!includesSomeLane(TransitionLane, InputContinuousLane) ✅
   ├─ Lane 不冲突!
   ├─ 暂停当前 render,workInProgress 保留 ✅
   └─ 切换到高优先级 lanes
  ↓
3. 高优先级任务完成
   ├─ renderRootConcurrent(root, InputContinuousLane)
   ├─ 只处理 ComponentB
   └─ commit 到 DOM
  ↓
4. 恢复低优先级任务
   ├─ 从保留的 workInProgress 继续 ✅
   ├─ 继续处理 ComponentA
   └─ 不需要重新构造 WIP

处理方式:

典型例子:

场景 3:Lane 冲突(必须重来)

中断原因:高优先级任务插入,且命中了当前 render lanes

关键点:高优先级更新会影响当前 WIP 的状态,必须重新构造

时间线:
1. 低优先级任务正在渲染
   ├─ root.workInProgress = div
   ├─ root.workInProgressRootRenderLanes = TransitionLane
   ├─ 正在处理 ComponentA
   └─ current 树:<App><div>A</div></App>
  ↓
2. 高优先级任务插入(用户点击)
   ├─ 新增 update:SyncLane 在 ComponentA
   ├─ 检查:includesSomeLane(TransitionLane, SyncLane) ✅
   ├─ Lane 冲突!
   ├─ 当前 WIP 作废 ❌
   └─ prepareFreshStack(root, SyncLane)
  ↓
3. 高优先级任务完成
   ├─ 从 root 重新构造 WIP
   ├─ 处理 SyncLane 更新
   └─ current 树更新:<App><div>C</div><span>New</span></App>
  ↓
4. 低优先级任务恢复
   ├─ 调用 renderRootConcurrent(root, TransitionLane)
   ├─ 检查:lane 冲突已解决
   ├─ prepareFreshStack(root, TransitionLane)
   │   └─ 从最新的 current 树重新创建 WIP
   └─ 所有节点都需要重新处理 ❌

处理方式:

function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
  // 丢弃旧的 workInProgress
  root.workInProgressRoot = null;
  root.workInProgress = null;
  
  // 从最新的 current 树重新创建 workInProgress
  root.workInProgressRoot = root;
  root.workInProgressRootRenderLanes = lanes;
  root.workInProgress = createWorkInProgress(root.current, null);
}

为什么 lane 冲突时必须重新构造?

核心原因:数据一致性

WIP 是在”特定 lanes 视角下的快照”。一旦新 update 属于当前 lanes 或会改变已 reconcile 过的 Fiber,那么已经构造的 WIP Fiber 的 memoizedState、updateQueue、child/sibling 都可能基于错误前提,只能整体作废。

举例说明:

// ComponentA 正在 render TransitionLane
function ComponentA() {
  const [count, setCount] = useState(0);  // 当前值:0
  
  // 假设 WIP 已经处理到这里,memoizedState.count = 0
  return <div>{count}</div>;
}

// 此时突然来了一个 SyncLane 更新:setCount(5)
// 如果继续用旧的 WIP:
// - WIP.memoizedState.count = 0 (错误!应该是 5)
// - child Fiber 的 props 是基于 count = 0 构造的 (错误!)

// 必须重新构造 WIP:
// - 从 current 树重新开始
// - 应用 SyncLane 更新:count = 5
// - 重新构造 child Fiber,props 基于 count = 5

Lane 冲突的典型场景

以下是所有会导致 lane 冲突、必须重新构造 WIP 的场景:

1. 同一组件的连续更新

最常见的场景:同一个组件在不同优先级下连续 setState

function Counter() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    // 低优先级更新(Transition)
    startTransition(() => {
      setCount(c => c + 1);  // TransitionLane
    });
    
    // 立即执行一个高优先级更新
    setTimeout(() => {
      setCount(c => c + 10);  // 默认 Lane(更高优先级)
    }, 50);
  };
  
  return <div onClick={handleClick}>{count}</div>;
}

冲突原因:

2. 父子组件的优先级交叉

场景:父组件低优先级更新进行中,子组件高优先级更新

function Parent() {
  const [data, setData] = useState({ value: 0 });
  
  const updateData = () => {
    startTransition(() => {
      setData({ value: 1 });  // TransitionLane
    });
  };
  
  return (
    <div>
      <Child data={data} />
    </div>
  );
}

function Child({ data }) {
  const [local, setLocal] = useState(0);
  
  // 用户交互触发高优先级更新
  const handleClick = () => {
    setLocal(1);  // InputContinuousLane 或 DefaultLane
  };
  
  return <div onClick={handleClick}>{data.value} - {local}</div>;
}

冲突原因:

3. Context 变化影响多个组件

场景:Context 更新影响正在渲染的组件树

const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    startTransition(() => {
      setTheme('dark');  // TransitionLane
    });
  };
  
  return (
    <ThemeContext.Provider value={theme}>
      <ComponentA />
      <ComponentB />  {/* 正在渲染这里 */}
      <ComponentC />
    </ThemeContext.Provider>
  );
}

function ComponentB() {
  const theme = useContext(ThemeContext);
  const [count, setCount] = useState(0);
  
  // 用户点击触发高优先级更新
  const handleClick = () => {
    setCount(c => c + 1);  // SyncLane
  };
  
  return <div onClick={handleClick}>{theme} - {count}</div>;
}

冲突原因:

4. useEffect/useLayoutEffect 触发的更新

场景:副作用触发的更新影响正在渲染的树

function Parent() {
  const [data, setData] = useState([]);
  
  const loadData = () => {
    startTransition(() => {
      setData([1, 2, 3]);  // TransitionLane
    });
  };
  
  return <Child data={data} onLoad={loadData} />;
}

function Child({ data, onLoad }) {
  const [status, setStatus] = useState('idle');
  
  useEffect(() => {
    if (data.length > 0) {
      // 高优先级更新
      setStatus('loaded');  // PassiveLane → 升级为 DefaultLane
    }
  }, [data]);
  
  return <div>{status}: {data.length}</div>;
}

冲突原因:

5. Suspense 边界的状态变化

场景:Suspense 从 pending 到 resolved 的过程中

function App() {
  const [query, setQuery] = useState('');
  
  const handleSearch = (value) => {
    startTransition(() => {
      setQuery(value);  // TransitionLane
    });
  };
  
  return (
    <Suspense fallback={<Spinner />}>
      <SearchResults query={query} />
    </Suspense>
  );
}

function SearchResults({ query }) {
  const data = use(fetchData(query));  // 可能 suspend
  return <div>{data}</div>;
}

冲突原因:

6. 同步更新打断并发更新

场景:ReactDOM.flushSync 强制同步更新

function App() {
  const [list, setList] = useState([1, 2, 3]);
  
  const updateList = () => {
    startTransition(() => {
      setList([...list, 4, 5, 6]);  // TransitionLane
    });
  };
  
  const forceSync = () => {
    ReactDOM.flushSync(() => {
      setList([10]);  // SyncLane
    });
  };
  
  return (
    <div>
      <button onClick={updateList}>Update</button>
      <button onClick={forceSync}>Force Sync</button>
      {list.map(i => <Item key={i} value={i} />)}
    </div>
  );
}

冲突原因:

7. 事件优先级提升

场景:离散事件(click)打断连续事件(scroll)

function App() {
  const [scrollY, setScrollY] = useState(0);
  const [clickCount, setClickCount] = useState(0);
  
  const handleScroll = (e) => {
    // 连续事件,较低优先级
    setScrollY(e.target.scrollTop);  // InputContinuousLane
  };
  
  const handleClick = () => {
    // 离散事件,较高优先级
    setClickCount(c => c + 1);  // SyncLane 或 InputDiscreteLane
  };
  
  return (
    <div onScroll={handleScroll} onClick={handleClick}>
      <div>Scroll: {scrollY}</div>
      <div>Clicks: {clickCount}</div>
    </div>
  );
}

冲突原因:

判断 Lane 冲突的核心条件

React 判断 lane 冲突的核心逻辑:

function checkLaneConflict(
  currentRenderLanes: Lanes,
  newUpdateLanes: Lanes
): boolean {
  // 1. 新 update 的 lanes 是否与当前 render lanes 有交集
  if (includesSomeLane(currentRenderLanes, newUpdateLanes)) {
    return true;  // 冲突
  }
  
  // 2. 新 update 影响的 Fiber 是否在当前 WIP 路径中
  if (isAncestorOf(newUpdateFiber, workInProgress)) {
    return true;  // 冲突
  }
  
  // 3. 新 update 影响的 Context 是否被当前 WIP 使用
  if (hasContextChanged(newUpdate, workInProgress)) {
    return true;  // 冲突
  }
  
  return false;  // 不冲突
}

总结:

只要满足以下任一条件,就会发生 lane 冲突,必须重新构造 WIP:

  1. 状态冲突:新 update 影响当前 WIP 已处理的 Fiber 的 state
  2. Props 冲突:新 update 改变当前 WIP 已处理的 Fiber 的 props
  3. Context 冲突:新 update 改变当前 WIP 依赖的 Context 值
  4. 树结构冲突:新 update 影响当前 WIP 的树结构(增删节点)
  5. 优先级强制:SyncLane 或 flushSync 强制同步更新

UpdateQueue 与 BaseQueue 的妙用

在中断与恢复过程中,React 通过 updateQueue、baseQueue 和 baseState 来保证状态的一致性和正确性。这是 React 能够跳过低优先级更新、处理优先级交叉的关键机制。

UpdateQueue 的结构

每个 Fiber 节点维护一个更新队列:

interface UpdateQueue<State> {
  // 基准状态:上次渲染确定的状态
  baseState: State;
  
  // 第一个 update(pending 队列的头)
  firstBaseUpdate: Update<State> | null;
  lastBaseUpdate: Update<State> | null;
  
  // 待处理的 update(新产生的 update)
  shared: {
    pending: Update<State> | null;  // 环形链表
  };
}

interface Update<State> {
  lane: Lane;                    // 更新的优先级
  action: State | ((s: State) => State);  // 更新函数
  next: Update<State> | null;    // 下一个 update
}

关键概念:

  1. baseState:上次渲染确定的基准状态
  2. baseQueue:被跳过的低优先级更新队列
  3. pending:新产生的待处理更新

场景:跳过低优先级更新

问题:如何在处理高优先级更新时,暂时跳过低优先级更新,但不丢失它们?

示例:

function Counter() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    // Update 1: 低优先级
    startTransition(() => {
      setCount(c => c + 1);  // TransitionLane, action: c => c + 1
    });
    
    // Update 2: 低优先级
    startTransition(() => {
      setCount(c => c * 2);  // TransitionLane, action: c => c * 2
    });
    
    // Update 3: 高优先级
    setCount(c => c + 10);  // DefaultLane, action: c => c + 10
  };
  
  return <div>{count}</div>;
}

时间线:

初始状态:
  count = 0
  updateQueue = {
    baseState: 0,
    baseQueue: null,
    shared.pending: null
  }

点击后,产生 3 个 update:
  updateQueue = {
    baseState: 0,
    baseQueue: null,
    shared.pending: Update3 -> Update1 -> Update2 -> Update3 (环形)
  }
  
  Update1: { lane: TransitionLane, action: c => c + 1 }
  Update2: { lane: TransitionLane, action: c => c * 2 }
  Update3: { lane: DefaultLane, action: c => c + 10 }

第一次渲染:处理高优先级(DefaultLane)

React 处理 DefaultLane 更新,跳过 TransitionLane 更新:

function processUpdateQueue(workInProgress, renderLanes) {
  const queue = workInProgress.updateQueue;
  
  // 1. 合并 pending 到 baseQueue
  let firstUpdate = queue.firstBaseUpdate;
  let lastUpdate = queue.lastBaseUpdate;
  let pendingQueue = queue.shared.pending;
  
  if (pendingQueue !== null) {
    // 拆开环形链表,合并到 baseQueue
    // firstUpdate -> Update1 -> Update2 -> Update3
  }
  
  // 2. 遍历 update 队列,跳过低优先级
  let newState = queue.baseState;  // 0
  let newBaseState = newState;
  let newBaseUpdate = null;
  let update = firstUpdate;
  
  while (update !== null) {
    const updateLane = update.lane;
    
    if (!isSubsetOfLanes(renderLanes, updateLane)) {
      // 优先级不够,跳过这个 update
      
      // 关键:保存到 baseQueue
      if (newBaseUpdate === null) {
        // 第一个被跳过的 update
        newBaseUpdate = update;
        // 保存当前状态作为 baseState
        newBaseState = newState;  // 0
      }
      
      // Update1 被跳过
      // newBaseState = 0
      // newBaseUpdate = Update1
    } else {
      // 优先级足够,应用这个 update
      
      // 关键:如果之前有跳过的 update,当前 update 也要保存到 baseQueue
      if (newBaseUpdate !== null) {
        // 克隆当前 update,但 lane 设为 NoLane(表示已处理)
        const clone = {
          lane: NoLane,
          action: update.action,
          next: null
        };
        // 追加到 baseQueue
      }
      
      // 应用 update
      newState = update.action(newState);
      // Update3: newState = 0 + 10 = 10 ✅
    }
    
    update = update.next;
  }
  
  // 3. 保存结果
  workInProgress.memoizedState = newState;  // 10
  workInProgress.updateQueue.baseState = newBaseState;  // 0 (关键!)
  workInProgress.updateQueue.firstBaseUpdate = newBaseUpdate;  // Update1
}

第一次渲染后的状态:

视觉结果:count = 10 ✅

Fiber 状态:
  memoizedState: 10          // 当前显示的状态
  updateQueue: {
    baseState: 0,            // 基准状态(被跳过的 update 的起点)
    baseQueue: Update1 -> Update2 -> Update3'  // 需要重新处理的 queue
  }
  
  Update1: { lane: TransitionLane, action: c => c + 1 }  // 被跳过
  Update2: { lane: TransitionLane, action: c => c * 2 }  // 被跳过
  Update3': { lane: NoLane, action: c => c + 10 }        // 已处理,但保留在 baseQueue

关键点:

  1. baseState = 0:保存第一个被跳过的 update 之前的状态
  2. baseQueue 保留所有 update:包括被跳过的和已处理的
  3. Update3’ 被克隆:虽然已经应用,但因为之前有被跳过的 update,所以也保留在 baseQueue

第二次渲染:处理低优先级(TransitionLane)

当 TransitionLane 恢复时,React 从 baseState 重新应用 baseQueue:

function processUpdateQueue(workInProgress, renderLanes) {
  const queue = workInProgress.updateQueue;
  
  // 1. 从 baseState 开始
  let newState = queue.baseState;  // 0 (关键!从基准状态开始)
  
  // 2. 遍历 baseQueue,这次处理 TransitionLane
  let update = queue.firstBaseUpdate;  // Update1
  
  while (update !== null) {
    const updateLane = update.lane;
    
    if (!isSubsetOfLanes(renderLanes, updateLane)) {
      // 优先级不够,跳过(但这次不会跳过任何 update)
    } else {
      // 应用 update
      newState = update.action(newState);
    }
    
    update = update.next;
  }
  
  // 应用顺序:
  // Update1: newState = 0 + 1 = 1    ✅
  // Update2: newState = 1 * 2 = 2    ✅
  // Update3': newState = 2 + 10 = 12 ✅ (重新应用)
  
  // 3. 保存结果
  workInProgress.memoizedState = newState;  // 12
  workInProgress.updateQueue.baseState = newState;  // 12
  workInProgress.updateQueue.firstBaseUpdate = null;  // 清空 baseQueue
}

第二次渲染后的状态:

视觉结果:count = 12 ✅

Fiber 状态:
  memoizedState: 12          // 最终状态
  updateQueue: {
    baseState: 12,           // 所有 update 都已处理
    baseQueue: null          // baseQueue 清空
  }

关键点:

  1. 从 baseState 重新开始:baseState = 0,保证所有 update 按正确顺序应用
  2. Update3’ 被重新应用:虽然之前应用过,但这次是在新的状态(2)上应用,结果不同
  3. 保证顺序正确:0 -> +1 -> *2 -> +10 = 12 ✅

为什么需要 BaseQueue?

问题:为什么不能只保存被跳过的 update?

答案:因为 update 之间可能有依赖关系。

反例:如果只保存被跳过的 update:

错误的处理方式:
第一次渲染(DefaultLane):
  应用 Update3: 0 + 10 = 10 ✅
  保存被跳过的:Update1, Update2

第二次渲染(TransitionLane):
  从 memoizedState = 10 开始(❌ 错误!)
  应用 Update1: 10 + 1 = 11
  应用 Update2: 11 * 2 = 22
  
  最终结果:22 ❌ 错误!
  
正确结果应该是:
  0 -> +1 -> *2 -> +10 = 12 ✅

正确的处理方式:

  1. 保存 baseState = 0(第一个被跳过的 update 之前的状态)
  2. 保存整个 baseQueue(包括被跳过的和已处理的)
  3. 恢复时从 baseState 重新应用所有 update

BaseQueue 与中断恢复的配合

场景 1:Lane 不冲突(WIP 保留)

1. 正在处理 TransitionLane
   ├─ ComponentA: memoizedState = 1, baseQueue = null ✅
   ├─ ComponentB: 正在处理,baseQueue = null
   └─ ComponentC: 未处理

2. DefaultLane 插入(不冲突)
   ├─ 暂停 TransitionLane
   ├─ WIP 保留 ✅
   └─ 处理 DefaultLane(只影响 ComponentD)

3. 恢复 TransitionLane
   ├─ ComponentA: 已处理,bailout ✅
   ├─ ComponentB: 继续处理
   └─ ComponentC: 开始处理
   
关键:baseQueue 保持不变,WIP 状态保留

场景 2:Lane 冲突(WIP 重建)

1. 正在处理 TransitionLane
   ├─ ComponentA: 
   │   updateQueue = {
   │     baseState: 0,
   │     baseQueue: Update1(TransitionLane)
   │   }
   └─ 正在处理...

2. DefaultLane 插入(冲突!)
   ├─ 中断 TransitionLane
   ├─ WIP 作废 ❌
   └─ 处理 DefaultLane:
       └─ ComponentA:
           processUpdateQueue(DefaultLane):
           ├─ 跳过 Update1(TransitionLane)
           ├─ 应用 Update2(DefaultLane)
           └─ baseQueue = Update1 -> Update2' ✅

3. 恢复 TransitionLane(重建 WIP)
   └─ ComponentA:
       processUpdateQueue(TransitionLane):
       ├─ 从 baseState 重新开始 ✅
       ├─ 应用 Update1(TransitionLane)
       ├─ 应用 Update2'(NoLane,重新应用)
       └─ baseQueue = null(所有 update 已处理)

关键:baseQueue 保证了 update 顺序和一致性

BaseQueue 的核心设计

三个关键点:

  1. baseState:第一个被跳过的 update 之前的状态,作为重新计算的起点
  2. baseQueue:保存所有需要重新处理的 update(包括被跳过的和已处理的)
  3. 重新应用:恢复时从 baseState 开始,重新应用 baseQueue 中的所有 update

保证的正确性:

// 伪代码表示
function calculateState(updates, renderLanes) {
  let state = baseState;
  for (const update of baseQueue) {
    if (shouldProcess(update.lane, renderLanes)) {
      state = update.action(state);
    }
  }
  return state;
}

// 无论如何中断和恢复,最终结果一致:
// calculateState(allUpdates, AllLanes) 
// === 
// calculateState(highPriorityUpdates, HighLanes) 
//     + calculateState(allUpdates, AllLanes)

总结:

BaseQueue 机制是 React 并发渲染的核心基础设施:

  1. 保证一致性:无论如何中断和恢复,最终状态一致
  2. 支持跳过:可以暂时跳过低优先级更新,但不丢失它们
  3. 正确的依赖:保证 update 之间的依赖关系正确
  4. 高效恢复:恢复时只需重新应用 baseQueue,不需要重新生成 update

这是 React 能够实现可中断、可恢复的并发渲染的关键机制之一。

对比总结

中断原因Lane 关系WIP 是否保留恢复方式性能影响
时间片用完相同 lanes✅ 保留从 WIP 继续,已处理节点 bailout高效,无需重新处理
高优先级插入Lane 不冲突✅ 保留暂停后从中断点恢复高效,React 18 核心优势
高优先级插入Lane 冲突❌ 被替换重新构造 WIP,baseQueue 保证一致性有开销,但保证一致性

Bailout 机制

当恢复时(WIP 保留的场景),React 通过 bailout 机制跳过已处理的节点:

function beginWork(current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes) {
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    
    // 检查是否可以复用(bailout)
    if (
      oldProps === newProps &&
      !hasLegacyContextChanged() &&
      !includesSomeLane(renderLanes, workInProgress.lanes)
    ) {
      // 可以复用,跳过处理
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
  }
  
  // 不能复用,重新处理
  return updateFunctionComponent(current, workInProgress, Component, newProps, renderLanes);
}

关键点:

满足条件时,直接复用已保存的状态,跳过处理。

总结

React 的中断与恢复机制的核心是 lane 冲突判断 和 BaseQueue 状态管理:

三种中断场景

  1. 时间片中断(相同 lanes):WIP 保留,从中断点继续
  2. Lane 不冲突:WIP 保留,暂停后恢复,这是 React 18 并发渲染的核心性能优势
  3. Lane 冲突:WIP 作废,必须重新构造,通过 BaseQueue 保证数据一致性

核心机制

1. 状态保存

2. BaseQueue 机制

3. 精确恢复

4. 一致性保证

整个过程支持时间分片和优先级调度,是实现并发渲染的基础。BaseQueue 机制确保了在复杂的优先级交叉场景下,状态始终保持一致和正确。