一、React Fiber 架构与任务调度详解

从架构设计角度深入理解如何解决前端 UI 卡顿问题

目录

  1. [问题背景:为什么需要 Fiber](#问题背景:为什么需要 Fiber "#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF")
  2. 核心概念与数据结构
  3. 时间切片机制
  4. 任务优先级调度
  5. 双缓存机制
  6. 架构设计思想提炼
  7. 实际应用场景

问题背景:为什么需要 Fiber {#问题背景}

React 15 的痛点

在 React 15 及之前的版本中,渲染过程是同步且不可中断的:

markdown 复制代码
用户点击 → setState → 开始 Reconciliation(协调)
                    ↓
            递归遍历整个组件树 (同步,不可中断)
                    ↓
            一次性更新所有 DOM
                    ↓
            浏览器重新绘制

问题场景:假设你有一个包含 1000 个组件的列表,用户点击按钮触发更新:

javascript 复制代码
// React 15 的行为
handleClick = () => {
  this.setState({ data: newData }); // 触发更新

  // 💥 主线程被阻塞 100ms+
  // - JS 引擎递归遍历 1000 个组件
  // - 对比虚拟 DOM
  // - 生成更新指令

  // ❌ 在这期间:
  // - 用户输入无响应
  // - 滚动卡顿
  // - 动画掉帧
}

根本原因

markdown 复制代码
┌─────────────────────────────────────────────────────────────┐
│                   浏览器主线程职责                           │
├─────────────────────────────────────────────────────────────┤
│  1. 执行 JavaScript                                         │
│  2. 样式计算(Style)                                       │
│  3. 布局计算(Layout)                                      │
│  4. 绘制(Paint)                                           │
│  5. 合成(Composite)                                       │
│  6. 处理用户输入(Input Events)                            │
└─────────────────────────────────────────────────────────────┘

⚠️ 问题:如果 JS 执行时间过长(如 React 递归渲染)
        → 其他任务(输入、动画)被饿死
        → 用户感知卡顿

💡 人眼感知:16.6ms 一帧(60fps)
           如果一帧内 JS 执行超过 16.6ms → 掉帧

架构层面的矛盾

css 复制代码
┌──────────────────────────────────────────────────────────────┐
│           React 15 的架构矛盾                                 │
├──────────────────────────────────────────────────────────────┤
│                                                               │
│  需求 A:快速响应用户交互(高优先级)                         │
│  需求 B:大量数据渲染计算(低优先级)                         │
│                                                               │
│  ❌ React 15:无法区分优先级,一视同仁                        │
│  ❌ 递归调用栈无法中断                                        │
│  ❌ 没有"让出控制权"的机制                                    │
│                                                               │
└──────────────────────────────────────────────────────────────┘

传统递归调用栈:
setState → render → render → render → ... → commitUpdate
           ↓         ↓         ↓              ↑
          无法中断   无法暂停  无法恢复      无法插队

核心概念与数据结构 {#核心概念}

Fiber:可中断的工作单元

Fiber 的本质是将递归的组件树转化为链表结构,让渲染过程变得可控。

对比:React 15 vs Fiber

javascript 复制代码
// ============================================
// React 15:递归的虚拟 DOM 树
// ============================================
const vdom = {
  type: 'div',
  props: { className: 'container' },
  children: [
    { type: 'h1', props: {}, children: ['Title'] },
    { type: 'p', props: {}, children: ['Content'] }
  ]
};

function reconcile(vdom) {
  // 💥 递归调用,无法中断
  const node = createDOMNode(vdom.type);
  vdom.children.forEach(child => {
    node.appendChild(reconcile(child)); // 深度递归
  });
  return node;
}

// ============================================
// Fiber:链表结构的工作单元
// ============================================
const fiber = {
  // 工作单元信息
  type: 'div',                    // 组件类型
  props: { className: 'container' },
  stateNode: domNode,             // 真实 DOM 引用

  // 链表指针(核心!)
  child: fiber1,                  // 第一个子节点
  sibling: fiber2,                // 下一个兄弟节点
  return: parentFiber,            // 父节点

  // 双缓存
  alternate: oldFiber,            // 指向另一棵树的对应节点

  // 副作用标记
  flags: Update | Placement,      // 需要执行的操作

  // 调度相关
  lanes: 0b0001,                  // 优先级标记

  // 状态
  memoizedState: { count: 0 },    // 上次渲染的 state
  updateQueue: [],                // 待处理的更新队列
};

为什么链表结构可以实现可中断?

scss 复制代码
传统递归(调用栈):
  function A() {
    B();  // 必须等 B 执行完
  }
  function B() {
    C();  // 必须等 C 执行完
  }
  ❌ 无法在 B 执行一半时跳出

Fiber 链表(数据结构):
  currentFiber = fiberA;

  while (currentFiber && !shouldYield()) {
    // 处理当前节点
    performUnitOfWork(currentFiber);

    // ✅ 检查时间,可以随时中断
    if (shouldYield()) {
      break; // 保存 currentFiber,下次继续
    }

    // 移动到下一个节点
    currentFiber = getNextFiber(currentFiber);
  }

Fiber 树的遍历规则(深度优先)

kotlin 复制代码
         Root
          │
     ┌────┴────┐
     │         │
    App      null
     │
  ┌──┴──┐
  │     │
Header Body
  │     │
 Nav  Content

遍历顺序:
1. Root → App (child)
2. App → Header (child)
3. Header → Nav (child)
4. Nav → null (no child),返回 Header
5. Header → Body (sibling)
6. Body → Content (child)
7. Content → null,返回 Body
8. Body → null (no sibling),返回 App
9. App → null,返回 Root
10. 结束

伪代码:
function getNextFiber(fiber) {
  // 1. 有子节点:向下走
  if (fiber.child) return fiber.child;

  // 2. 无子节点:找兄弟节点
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) return nextFiber.sibling;
    nextFiber = nextFiber.return; // 向上回溯
  }

  return null; // 遍历完成
}

时间切片机制 {#时间切片}

核心思想:主动让出控制权

markdown 复制代码
传统长任务(100ms):
┌──────────────────────────────────────────────────────────────┐
│                    JS 执行(阻塞)                             │
└──────────────────────────────────────────────────────────────┘
                      ↓
              用户交互无响应

时间切片(5ms × 20):
┌────┐     ┌────┐     ┌────┐     ┌────┐     ┌────┐
│ JS │空闲 │ JS │空闲 │ JS │空闲 │ JS │空闲 │ JS │  ...
└────┘     └────┘     └────┘     └────┘     └────┘
  ↑          ↑          ↑          ↑
  5ms     浏览器可以    5ms     处理其他任务
         处理输入/绘制

实现原理:MessageChannel + 宏任务

React 不使用 setTimeout,而是使用 MessageChannel

javascript 复制代码
// ============================================
// React Scheduler 的时间切片实现(简化版)
// ============================================

// 1. 任务队列(最小堆,按过期时间排序)
const taskQueue = [];
let currentTask = null;
let isPerformingWork = false;

// 2. 时间切片配置
const FRAME_INTERVAL = 5; // 5ms 切片
let deadline = 0;

// 3. 判断是否应该让出控制权
function shouldYield() {
  return performance.now() >= deadline;
}

// 4. 调度入口
function scheduleCallback(priority, callback) {
  const currentTime = performance.now();

  // 计算过期时间(根据优先级)
  let timeout;
  switch (priority) {
    case ImmediatePriority:  // 立即执行(如用户输入)
      timeout = -1;
      break;
    case UserBlockingPriority: // 250ms
      timeout = 250;
      break;
    case NormalPriority: // 5000ms
      timeout = 5000;
      break;
    case LowPriority: // 10000ms
      timeout = 10000;
      break;
    case IdlePriority: // 永不过期
      timeout = maxSigned31BitInt;
      break;
  }

  const expirationTime = currentTime + timeout;

  // 创建任务
  const newTask = {
    callback,
    priority,
    expirationTime,
    startTime: currentTime
  };

  // 插入任务队列(最小堆)
  push(taskQueue, newTask);

  // 请求调度
  if (!isPerformingWork) {
    requestHostCallback(workLoop);
  }

  return newTask;
}

// 5. 使用 MessageChannel 实现宏任务
const channel = new MessageChannel();
const port = channel.port2;

channel.port1.onmessage = () => {
  if (scheduledCallback !== null) {
    const currentTime = performance.now();
    deadline = currentTime + FRAME_INTERVAL; // 设置本次切片的截止时间

    const hasMoreWork = scheduledCallback(currentTime);

    if (hasMoreWork) {
      // 还有任务,继续调度
      port.postMessage(null);
    } else {
      scheduledCallback = null;
    }
  }
};

let scheduledCallback = null;

function requestHostCallback(callback) {
  scheduledCallback = callback;
  port.postMessage(null); // 触发宏任务
}

// 6. 工作循环
function workLoop(initialTime) {
  let currentTime = initialTime;
  currentTask = peek(taskQueue); // 取优先级最高的任务

  while (currentTask !== null) {
    // 检查是否应该让出控制权
    if (currentTask.expirationTime > currentTime && shouldYield()) {
      // 时间用完,但任务未过期 → 中断
      break;
    }

    const callback = currentTask.callback;
    if (callback !== null) {
      currentTask.callback = null;

      // 执行任务
      const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
      const continuationCallback = callback(didUserCallbackTimeout);

      if (typeof continuationCallback === 'function') {
        // 任务返回函数 → 任务未完成,下次继续
        currentTask.callback = continuationCallback;
      } else {
        // 任务完成,移出队列
        if (currentTask === peek(taskQueue)) {
          pop(taskQueue);
        }
      }
    } else {
      pop(taskQueue);
    }

    currentTask = peek(taskQueue);
    currentTime = performance.now();
  }

  // 返回是否还有任务
  return currentTask !== null;
}

为什么用 MessageChannel 而不是 setTimeout?

javascript 复制代码
┌────────────────────────────────────────────────────────────┐
│            setTimeout vs MessageChannel                    │
├────────────────────────────────────────────────────────────┤
│                                                             │
│  setTimeout(fn, 0)                                         │
│  ❌ 最小延迟 4ms(浏览器限制)                              │
│  ❌ 嵌套 5 层后强制 4ms                                     │
│  ❌ 精度不够                                                │
│                                                             │
│  MessageChannel                                            │
│  ✅ 宏任务,不受 4ms 限制                                   │
│  ✅ 精度更高                                                │
│  ✅ 可以立即执行                                            │
│                                                             │
│  requestIdleCallback                                       │
│  ❌ 兼容性差                                                │
│  ❌ 触发频率不稳定(可能 50ms+)                            │
│  ❌ 浏览器控制,不够可控                                    │
│                                                             │
└────────────────────────────────────────────────────────────┘

任务优先级调度 {#任务调度}

优先级系统设计

React 使用 Lane 模型(车道模型)来表示优先级:

scss 复制代码
二进制车道模型(31 位):
┌────────────────────────────────────────────────────────────┐
│  0b0000000000000000000000000000001  SyncLane (同步车道)    │
│  0b0000000000000000000000000000010  InputContinuousLane    │
│  0b0000000000000000000000000001000  DefaultLane            │
│  0b0000000000000000000001000000000  TransitionLane         │
│  0b0000000000000100000000000000000  RetryLane              │
│  0b0001000000000000000000000000000  IdleLane (空闲车道)    │
└────────────────────────────────────────────────────────────┘

为什么用二进制?
✅ 按位或(|)可以合并多个优先级
✅ 按位与(&)可以判断是否包含某个优先级
✅ 快速找到最高优先级(getHighestPriorityLane)

优先级分类与场景

javascript 复制代码
// ============================================
// 优先级分类
// ============================================

// 1. 同步优先级(SyncLane)
// 场景:必须立即执行的更新
// - ReactDOM.render()
// - useEffect 的 cleanup
// - 错误边界
function handleError() {
  // 立即同步执行,不走调度
  ReactDOM.flushSync(() => {
    setError(true);
  });
}

// 2. 输入连续优先级(InputContinuousLane)
// 场景:用户连续输入
// - onChange
// - onMouseMove
// - onScroll
<input onChange={(e) => {
  // 高优先级,快速响应
  setSearchText(e.target.value);
}} />

// 3. 默认优先级(DefaultLane)
// 场景:普通异步更新
// - 网络请求完成
// - setTimeout
// - useEffect 的副作用
useEffect(() => {
  fetch('/api/data').then(data => {
    setData(data); // 默认优先级
  });
}, []);

// 4. 过渡优先级(TransitionLane)
// 场景:可以延后的大量更新
// - 列表渲染
// - 页面切换动画
import { useTransition } from 'react';

function SearchPage() {
  const [isPending, startTransition] = useTransition();

  const handleSearch = (text) => {
    // 高优先级:立即更新输入框
    setInputValue(text);

    // 低优先级:延后搜索结果渲染
    startTransition(() => {
      setSearchResults(filterData(text)); // 1000+ 条数据
    });
  };
}

// 5. 空闲优先级(IdleLane)
// 场景:可有可无的更新
// - 日志上报
// - 预加载

优先级调度流程

ini 复制代码
用户点击按钮(触发多个更新)
        ↓
┌──────────────────────────────────────────────────────────┐
│                更新队列(Update Queue)                   │
├──────────────────────────────────────────────────────────┤
│  Update1: lane = SyncLane        (输入框更新)            │
│  Update2: lane = DefaultLane     (网络请求结果)          │
│  Update3: lane = TransitionLane  (列表渲染)              │
└──────────────────────────────────────────────────────────┘
        ↓
ensureRootIsScheduled() // 确保根节点被调度
        ↓
┌──────────────────────────────────────────────────────────┐
│              计算下一个要处理的 Lane                      │
├──────────────────────────────────────────────────────────┤
│  getNextLanes(root, NoLanes)                             │
│    → 返回 SyncLane (最高优先级)                          │
└──────────────────────────────────────────────────────────┘
        ↓
根据 Lane 选择调度方式
        ↓
┌──────────────────────────────────────────────────────────┐
│  if (lane === SyncLane) {                                │
│    scheduleSyncCallback(performSyncWorkOnRoot);          │
│    // 同步执行,不走调度器                                │
│  } else {                                                │
│    scheduleCallback(                                     │
│      lanesToSchedulerPriority(lane),                     │
│      performConcurrentWorkOnRoot                         │
│    );                                                    │
│    // 异步执行,进入 Scheduler 调度                       │
│  }                                                       │
└──────────────────────────────────────────────────────────┘
        ↓
进入 Render 阶段(只处理当前 Lane 的更新)
        ↓
如果渲染过程中有更高优先级的更新插入
        ↓
┌──────────────────────────────────────────────────────────┐
│  if (newLane 优先级更高) {                                │
│    // 丢弃当前工作(workInProgress)                      │
│    workInProgress = null;                                │
│    // 重新调度                                            │
│    ensureRootIsScheduled(root);                          │
│  }                                                       │
└──────────────────────────────────────────────────────────┘

优先级饥饿问题的解决

javascript 复制代码
// 问题:低优先级任务可能永远得不到执行

// 解决方案:过期时间 (Expiration Time)
function markStarvedLanesAsExpired(root, currentTime) {
  const pendingLanes = root.pendingLanes;
  const expirationTimes = root.expirationTimes;

  let lanes = pendingLanes;
  while (lanes > 0) {
    const index = pickArbitraryLaneIndex(lanes);
    const lane = 1 << index;
    const expirationTime = expirationTimes[index];

    if (expirationTime === NoTimestamp) {
      // 首次进入,设置过期时间
      expirationTimes[index] = computeExpirationTime(lane, currentTime);
    } else if (expirationTime <= currentTime) {
      // 已过期 → 提升为同步优先级
      root.expiredLanes |= lane;
    }

    lanes &= ~lane; // 移除已检查的 lane
  }
}

// 过期时间计算(根据优先级)
function computeExpirationTime(lane, currentTime) {
  switch (lane) {
    case SyncLane:
      return currentTime; // 立即过期
    case InputContinuousLane:
      return currentTime + 250; // 250ms
    case DefaultLane:
      return currentTime + 5000; // 5s
    case TransitionLane:
      return currentTime + 10000; // 10s
    case IdleLane:
      return NoTimestamp; // 永不过期
  }
}

双缓存机制 {#双缓存}

为什么需要双缓存?

python 复制代码
问题场景:渲染过程被中断
┌──────────────────────────────────────────────────────────┐
│  用户看到的 UI(当前 DOM)                                │
│  ┌────────┐  ┌────────┐                                  │
│  │ Header │  │  Body  │                                  │
│  └────────┘  └────────┘                                  │
└──────────────────────────────────────────────────────────┘
        ↓ 触发更新
┌──────────────────────────────────────────────────────────┐
│  React 正在构建新的 UI(50% 完成)                        │
│  ┌────────┐  ┌────────┐  ┌─────                         │
│  │ Header'│  │  Body' │  │ Footer (渲染中...)            │
│  └────────┘  └────────┘  └─────                         │
└──────────────────────────────────────────────────────────┘
        ↓ 高优先级任务插入,中断渲染

❌ 如果直接在当前树上修改 → 用户会看到半成品
✅ 双缓存:在内存中构建完整新树 → 原子切换

双缓存结构

javascript 复制代码
// ============================================
// FiberRoot 结构
// ============================================
const fiberRoot = {
  // 当前屏幕显示的树
  current: currentFiber, // HostRootFiber

  // 其他属性
  containerInfo: domContainer, // 真实 DOM 容器
  pendingLanes: 0b0000, // 待处理的优先级
  finishedWork: null, // 已完成的工作树
};

// ============================================
// Current 树与 WorkInProgress 树
// ============================================

// Current Fiber(当前显示的树)
const currentFiber = {
  type: 'div',
  stateNode: domNode, // 指向真实 DOM
  child: currentChildFiber,
  alternate: workInProgressFiber, // 指向另一棵树
  memoizedState: { count: 0 }, // 当前状态
};

// WorkInProgress Fiber(正在构建的树)
const workInProgressFiber = {
  type: 'div',
  stateNode: domNode, // 复用 DOM 节点
  child: workInProgressChildFiber,
  alternate: currentFiber, // 指回 current 树
  memoizedState: { count: 1 }, // 新状态
  flags: Update, // 标记需要更新
};

双缓存切换流程

javascript 复制代码
// ============================================
// 完整的双缓存流程
// ============================================

// 1. 初始渲染(Mount)
function performSyncWorkOnRoot(root) {
  // 创建 workInProgress 树
  const current = root.current; // HostRootFiber(空树)
  const workInProgress = createWorkInProgress(current, null);

  // 开始构建
  renderRootSync(root, workInProgress);

  // 构建完成
  root.finishedWork = workInProgress;

  // 提交阶段
  commitRoot(root);
}

// 2. createWorkInProgress(创建或复用)
function createWorkInProgress(current, pendingProps) {
  let workInProgress = current.alternate;

  if (workInProgress === null) {
    // 首次渲染:创建新 Fiber
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode
    );

    // 建立双向链接
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    // 更新:复用上次的 workInProgress
    workInProgress.pendingProps = pendingProps;
    workInProgress.flags = NoFlags; // 清空副作用
    workInProgress.subtreeFlags = NoFlags;
  }

  // 复用不变的属性
  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;

  return workInProgress;
}

// 3. commitRoot(原子切换)
function commitRoot(root) {
  const finishedWork = root.finishedWork;

  // Before Mutation 阶段
  commitBeforeMutationEffects(finishedWork);

  // Mutation 阶段(修改真实 DOM)
  commitMutationEffects(finishedWork, root);

  // ✨ 关键:切换 current 指针(原子操作)
  root.current = finishedWork;

  // Layout 阶段(执行生命周期)
  commitLayoutEffects(finishedWork, root);
}

双缓存优化:复用 Fiber 节点

javascript 复制代码
// ============================================
// Diff 算法中的 Fiber 复用
// ============================================

function reconcileChildFibers(
  returnFiber,
  currentFirstChild,
  newChild
) {
  // 场景 1:单节点 Diff
  if (typeof newChild === 'object' && newChild !== null) {
    if (newChild.key !== null) {
      // 尝试复用
      const existing = currentFirstChild;

      if (
        existing !== null &&
        existing.key === newChild.key &&
        existing.type === newChild.type
      ) {
        // ✅ 可以复用!
        const clone = useFiber(existing, newChild.props);
        clone.return = returnFiber;
        return clone;
      }
    }
  }

  // 场景 2:多节点 Diff
  if (Array.isArray(newChild)) {
    return reconcileChildrenArray(
      returnFiber,
      currentFirstChild,
      newChild
    );
  }

  // 无法复用:创建新 Fiber
  const created = createFiberFromElement(newChild);
  created.return = returnFiber;
  return created;
}

// useFiber:复用 Fiber 节点
function useFiber(fiber, pendingProps) {
  const clone = createWorkInProgress(fiber, pendingProps);
  clone.index = 0;
  clone.sibling = null;
  return clone;
}

内存占用分析

yaml 复制代码
┌────────────────────────────────────────────────────────────┐
│              双缓存的内存开销                               │
├────────────────────────────────────────────────────────────┤
│                                                             │
│  假设组件树有 1000 个节点                                   │
│                                                             │
│  单树内存占用:                                             │
│    1000 节点 × 约 200 字节/节点 = 200KB                     │
│                                                             │
│  双树内存占用:                                             │
│    Current 树: 200KB                                        │
│    WorkInProgress 树: 200KB                                 │
│    总计: 400KB                                              │
│                                                             │
│  ✅ 优化点:                                                │
│    1. stateNode(真实 DOM)是共享的,不重复                 │
│    2. 没有变化的子树可以共享(通过 alternate 指针)         │
│    3. 实际占用 < 2 倍                                       │
│                                                             │
└────────────────────────────────────────────────────────────┘

架构设计思想提炼 {#架构思想}

1. 将递归转为循环(可控性)

scss 复制代码
设计模式:Continuation-Passing Style (CPS)

❌ 递归调用栈(不可控):
function traverse(node) {
  if (node.children) {
    node.children.forEach(traverse); // 深度递归
  }
}

✅ 显式维护工作栈(可控):
function traverseIterative(root) {
  const stack = [root];

  while (stack.length > 0) {
    const node = stack.pop();

    // ✨ 可以在这里中断
    if (shouldPause()) {
      saveState(stack);
      return; // 下次继续
    }

    if (node.children) {
      stack.push(...node.children);
    }
  }
}

应用场景:
- 大型数据结构遍历
- 复杂计算任务拆分
- 游戏引擎的渲染循环

2. 优先级调度系统

diff 复制代码
设计模式:优先级队列 + 动态调度

核心组件:
┌────────────────────────────────────────────────────────────┐
│  1. 任务队列(Priority Queue / Min Heap)                  │
│     - 按优先级排序                                          │
│     - O(log n) 插入和删除                                   │
│                                                             │
│  2. 优先级计算器(Priority Calculator)                    │
│     - 根据任务类型分配初始优先级                            │
│     - 动态调整(防止饥饿)                                  │
│                                                             │
│  3. 调度器(Scheduler)                                     │
│     - 时间切片                                              │
│     - 抢占式调度                                            │
│                                                             │
│  4. 任务执行器(Task Executor)                             │
│     - 可中断/恢复                                           │
│     - 返回延续(Continuation)                              │
└────────────────────────────────────────────────────────────┘

应用场景:
- 前端任务调度(动画、网络请求、用户交互)
- 后端任务队列(消息队列、作业调度)
- 操作系统进程调度

3. 双缓冲技术

markdown 复制代码
设计模式:Double Buffering

原理:
┌────────────────────────────────────────────────────────────┐
│  Buffer A(前台)          Buffer B(后台)                │
│  ┌─────────────┐          ┌─────────────┐                 │
│  │ 当前显示    │          │ 正在构建    │                 │
│  │ 的内容      │          │ 的内容      │                 │
│  └─────────────┘          └─────────────┘                 │
│        ↓                        ↓                          │
│     用户可见              用户不可见                       │
└────────────────────────────────────────────────────────────┘
                    ↓ 构建完成
┌────────────────────────────────────────────────────────────┐
│  指针交换(原子操作)                                       │
│  Buffer A ←→ Buffer B                                      │
└────────────────────────────────────────────────────────────┘

应用场景:
- 游戏渲染(避免画面撕裂)
- 视频播放(平滑切换帧)
- 数据库事务(MVCC 多版本并发控制)
- 编辑器(Undo/Redo)

4. 时间切片 + 协同式多任务

scss 复制代码
设计模式:Cooperative Multitasking

对比:
┌────────────────────────────────────────────────────────────┐
│  抢占式多任务(Preemptive)                                │
│  - 操作系统强制中断任务                                    │
│  - 任务不需要主动让出                                      │
│  - 需要操作系统支持                                        │
│                                                             │
│  协同式多任务(Cooperative)                               │
│  - 任务主动检查并让出控制权                                │
│  - shouldYield() → 返回控制权                              │
│  - 适合单线程环境(如浏览器主线程)                        │
└────────────────────────────────────────────────────────────┘

实现模式:
function* longRunningTask() {
  for (let i = 0; i < 10000; i++) {
    doWork(i);

    if (i % 100 === 0 && shouldYield()) {
      yield; // 让出控制权
    }
  }
}

const task = longRunningTask();

function workLoop() {
  while (!task.next().done && !shouldYield()) {
    // 继续执行
  }

  if (!task.done) {
    requestIdleCallback(workLoop); // 下一帧继续
  }
}

5. 链表数据结构的妙用

kotlin 复制代码
Fiber 链表的优势:
┌────────────────────────────────────────────────────────────┐
│  1. 内存友好                                                │
│     - 不需要额外的栈空间                                    │
│     - 避免递归调用栈溢出                                    │
│                                                             │
│  2. 灵活遍历                                                │
│     - 可以从任意节点开始                                    │
│     - 支持双向遍历(child/return)                          │
│                                                             │
│  3. 可中断                                                  │
│     - 保存当前节点指针即可恢复                              │
│     - 不需要保存整个调用栈                                  │
│                                                             │
│  4. 支持优先级                                              │
│     - 可以跳过低优先级子树                                  │
│     - 动态调整遍历路径                                      │
└────────────────────────────────────────────────────────────┘

对比树结构:
// 树:自上而下,必须遍历所有子节点
{
  children: [child1, child2, child3]
}

// 链表:灵活控制遍历路径
{
  child: firstChild,      // 只存第一个子节点
  sibling: nextSibling,   // 兄弟节点
  return: parent          // 父节点
}

实际应用场景 {#应用场景}

场景 1:大列表优化

javascript 复制代码
// ============================================
// 问题:渲染 10000 条数据导致页面卡顿
// ============================================

// ❌ 传统做法:一次性渲染所有数据
function BadList({ items }) {
  return (
    <div>
      {items.map(item => <ListItem key={item.id} data={item} />)}
    </div>
  );
  // 💥 10000 个组件同时渲染 → 阻塞主线程
}

// ✅ 方案 1:使用 useTransition(利用 Fiber 的优先级调度)
import { useState, useTransition } from 'react';

function GoodList({ items }) {
  const [filteredItems, setFilteredItems] = useState(items);
  const [isPending, startTransition] = useTransition();

  const handleFilter = (keyword) => {
    // 高优先级:立即更新输入框
    setKeyword(keyword);

    // 低优先级:延后列表过滤
    startTransition(() => {
      const filtered = items.filter(item =>
        item.name.includes(keyword)
      );
      setFilteredItems(filtered);
    });
  };

  return (
    <>
      <input onChange={(e) => handleFilter(e.target.value)} />
      {isPending && <Spinner />}
      <div>
        {filteredItems.map(item => <ListItem key={item.id} data={item} />)}
      </div>
    </>
  );
}

// ✅ 方案 2:虚拟列表(只渲染可见区域)
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          <ListItem data={items[index]} />
        </div>
      )}
    </FixedSizeList>
  );
}

场景 2:搜索框优化(防抖 + 优先级)

javascript 复制代码
// ============================================
// 问题:用户输入时触发大量搜索请求
// ============================================

function SearchBox() {
  const [inputValue, setInputValue] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const value = e.target.value;

    // 高优先级:立即更新输入框(用户必须看到输入)
    setInputValue(value);

    // 低优先级:搜索结果可以延后
    startTransition(() => {
      // 模拟大量计算或网络请求
      const results = performExpensiveSearch(value);
      setSearchResults(results);
    });
  };

  return (
    <>
      <input
        value={inputValue}
        onChange={handleChange}
        placeholder="搜索..."
      />
      {isPending && <Spinner />}
      <SearchResults results={searchResults} />
    </>
  );
}

// Fiber 的作用:
// 1. 输入框更新是 InputContinuousLane(高优先级),立即响应
// 2. 搜索结果是 TransitionLane(低优先级),可被中断
// 3. 如果用户继续输入,会中断之前的搜索渲染,开始新的搜索

场景 3:动画 + 数据更新

javascript 复制代码
// ============================================
// 问题:数据更新导致动画卡顿
// ============================================

import { useState, useDeferredValue } from 'react';

function AnimatedDashboard() {
  const [data, setData] = useState(initialData);

  // 延迟的数据(低优先级)
  const deferredData = useDeferredValue(data);

  return (
    <>
      {/* 高优先级:动画始终流畅 */}
      <AnimatedChart data={data} />

      {/* 低优先级:数据表格可以延后渲染 */}
      <DataTable data={deferredData} />
    </>
  );
}

// useDeferredValue 原理:
// 1. 返回一个"延迟"的值
// 2. 当有高优先级更新时,延迟值不会立即更新
// 3. 等高优先级任务完成后,再用 TransitionLane 更新延迟值

场景 4:自定义任务调度器

javascript 复制代码
// ============================================
// 在你的项目中实现类似 Fiber 的调度
// ============================================

class TaskScheduler {
  constructor() {
    this.taskQueue = [];
    this.isRunning = false;
    this.frameDeadline = 0;
  }

  // 调度任务
  scheduleTask(task, priority = 'normal') {
    const taskObj = {
      task,
      priority,
      expirationTime: this.calculateExpiration(priority)
    };

    this.taskQueue.push(taskObj);
    this.taskQueue.sort((a, b) => a.expirationTime - b.expirationTime);

    if (!this.isRunning) {
      this.start();
    }
  }

  calculateExpiration(priority) {
    const now = performance.now();
    switch (priority) {
      case 'immediate':
        return now;
      case 'high':
        return now + 250;
      case 'normal':
        return now + 5000;
      case 'low':
        return now + 10000;
      default:
        return now + 5000;
    }
  }

  start() {
    this.isRunning = true;
    this.workLoop();
  }

  shouldYield() {
    return performance.now() >= this.frameDeadline;
  }

  workLoop() {
    this.frameDeadline = performance.now() + 5; // 5ms 时间切片

    while (this.taskQueue.length > 0 && !this.shouldYield()) {
      const taskObj = this.taskQueue.shift();

      try {
        const result = taskObj.task();

        // 如果任务返回函数,说明未完成
        if (typeof result === 'function') {
          this.taskQueue.unshift({
            task: result,
            priority: taskObj.priority,
            expirationTime: taskObj.expirationTime
          });
        }
      } catch (error) {
        console.error('Task error:', error);
      }
    }

    // 还有任务,继续调度
    if (this.taskQueue.length > 0) {
      requestIdleCallback(() => this.workLoop());
    } else {
      this.isRunning = false;
    }
  }
}

// 使用示例
const scheduler = new TaskScheduler();

// 高优先级任务
scheduler.scheduleTask(() => {
  console.log('处理用户输入');
}, 'high');

// 低优先级任务(可拆分)
let index = 0;
const processData = () => {
  // 处理 100 条数据
  for (let i = 0; i < 100; i++) {
    if (index >= data.length) return; // 完成
    processItem(data[index++]);
  }

  // 返回函数表示还有任务
  return processData;
};

scheduler.scheduleTask(processData, 'low');

总结:架构设计的核心启示

php 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                  Fiber 架构的设计智慧                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 问题拆解                                                     │
│     将大任务拆解为小单元 → 可控、可中断                         │
│                                                                  │
│  2. 优先级思维                                                   │
│     不是所有任务都同等重要 → 区分轻重缓急                       │
│                                                                  │
│  3. 空间换时间                                                   │
│     双缓存多占内存 → 换取流畅体验                               │
│                                                                  │
│  4. 延迟计算                                                     │
│     不急的任务可以延后 → 先保证核心体验                         │
│                                                                  │
│  5. 数据结构选择                                                 │
│     递归 → 链表 → 可控性质变                                    │
│                                                                  │
│  6. 协同式设计                                                   │
│     主动让出控制权 → 在约束条件下达成目标                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

通过学习 Fiber 架构,你可以将这些思想应用到:

  • 前端性能优化:长列表、复杂交互
  • 后端任务调度:消息队列、作业系统
  • 系统设计:分布式任务调度、流量控制
  • 游戏开发:游戏循环、渲染管线

核心理念:在约束条件下(单线程),通过合理的架构设计(拆分、调度、双缓存)实现复杂目标(流畅体验)

相关推荐
小璞28 分钟前
四、虚拟 DOM 与 Diff 算法:架构设计的智慧
前端·react.js·前端框架
南蓝30 分钟前
【AI 日记】调用大模型的时候如何按照 sse 格式输出
前端·人工智能
一树论32 分钟前
浏览器插件开发经验分享二:如何处理日期控件
前端·javascript
小璞32 分钟前
六、React 并发模式:让应用"感觉"更快的架构智慧
前端·react.js·架构
Yanni4Night33 分钟前
LogTape:零依赖的现代JavaScript日志解决方案
前端·javascript
疯狂踩坑人33 分钟前
Node写MCP入门教程
前端·agent·mcp
重铸码农荣光33 分钟前
一文吃透 ES6 Symbol:JavaScript 里的「独一无二」标识符
前端·javascript
申阳34 分钟前
Day 15:01. 基于 Tauri 2.0 开发后台管理系统-Tauri 2.0 初探
前端·后端·程序员
想吃电饭锅34 分钟前
前端大厦建立在并不牢固的地基,浅谈JavaScript未来
前端