首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >从 0 实现 React 18:手写并发模式与 Fiber 架构核心

从 0 实现 React 18:手写并发模式与 Fiber 架构核心

原创
作者头像
用户12339161
发布2026-08-21 15:21:35
发布2026-08-21 15:21:35
890
举报

React 18 的并发渲染(Concurrent Rendering)是其最革命性的特性。它并非简单的新 API,而是一次架构级的重写——Fiber 架构赋予了 React 可中断、可恢复的渲染能力,使时间切片和优先级调度成为可能。本文将带你从零实现一个精简版 React 18,涵盖 Fiber 数据结构协调(Reconciliation)并发调度双缓冲(Double Buffering)Hooks 工作原理


1. Fiber 节点的数据结构

Fiber 是 React 18 中最小的工作单元,它是一个 JavaScript 对象,通过链表结构(child、sibling、return)描述组件树,使得更新工作可被拆分和中断。

代码语言:javascript
复制
type FiberNode = {
  // 核心标识
  tag: 'host_root' | 'host_component' | 'function_component' | 'host_text';
  key: string | null;
  type: string | Function;  // 'div' 或组件函数
  
  // 状态与 props
  pendingProps: any;
  memoizedProps: any;
  memoizedState: any;       // 对于 FC,存储 Hook 链表
  
  // 链表结构
  return: FiberNode | null;
  child: FiberNode | null;
  sibling: FiberNode | null;
  index: number;
  
  // 双缓冲与副作用
  alternate: FiberNode | null;   // 指向另一棵树的对应节点
  flags: 'update' | 'delete' | 'placement' | 'layout_effect' | null;
  deletions: FiberNode[] | null;
  
  // 调度相关(React 18 核心)
  lanes: number;           // 优先级位掩码
  childLanes: number;
};

每个 Fiber 节点通过 alternate 指针连接 current 树和 workInProgress 树,实现双缓冲渲染。


2. 并发调度与时间切片

React 18 使用 MessageChannel 实现宏任务调度,在浏览器空闲时执行工作单元。以下实现 workLoop,它会在渲染超过 5ms 时主动让出主线程:

代码语言:javascript
复制
const DEADLINE = 5; // 毫秒
const channel = new MessageChannel();
let pendingCallback: (() => void) | null = null;

channel.port2.onmessage = () => {
  const callback = pendingCallback;
  pendingCallback = null;
  if (callback) callback();
};

function scheduleCallback(callback: () => void) {
  pendingCallback = callback;
  channel.port1.postMessage(null);
}

let nextUnitOfWork: FiberNode | null = null;
let workInProgressRoot: FiberNode | null = null;

function performUnitOfWork(fiber: FiberNode): FiberNode | null {
  // 1. 协调(reconcile)子节点
  reconcileChildren(fiber);
  // 2. 如果有子节点,返回子节点继续工作
  if (fiber.child) return fiber.child;
  // 3. 否则找兄弟节点,或向上回溯
  let next: FiberNode | null = fiber;
  while (next) {
    if (next.sibling) return next.sibling;
    next = next.return;
  }
  return null;
}

function workLoop(deadline: number) {
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = performance.now() >= deadline;
  }
  if (nextUnitOfWork) {
    scheduleCallback(() => workLoop(performance.now() + DEADLINE));
  } else {
    // 所有工作完成,提交到 DOM
    commitRoot(workInProgressRoot!);
  }
}

export function render(element: JSX.Element, container: HTMLElement) {
  // 创建 root Fiber
  const rootFiber: FiberNode = {
    tag: 'host_root',
    type: null,
    key: null,
    pendingProps: element,
    memoizedProps: null,
    memoizedState: null,
    return: null,
    child: null,
    sibling: null,
    index: 0,
    alternate: null,
    flags: null,
    deletions: null,
    lanes: 0,
    childLanes: 0,
  };
  workInProgressRoot = rootFiber;
  nextUnitOfWork = rootFiber;
  scheduleCallback(() => workLoop(performance.now() + DEADLINE));
}

3. 协调(Reconciliation)与 Diff

协调是 React 的灵魂。我们在 reconcileChildren 中比较新旧 Fiber 子节点,决定是复用、更新还是创建新节点。

代码语言:javascript
复制
function reconcileChildren(workInProgress: FiberNode) {
  const children = workInProgress.pendingProps.children;
  // 简化版:仅处理单子节点
  if (typeof children === 'string' || typeof children === 'number') {
    // 文本节点
    const newFiber: FiberNode = {
      tag: 'host_text',
      type: null,
      key: null,
      pendingProps: children,
      memoizedProps: null,
      memoizedState: null,
      return: workInProgress,
      child: null,
      sibling: null,
      index: 0,
      alternate: null,
      flags: 'update',
      deletions: null,
      lanes: 0,
      childLanes: 0,
    };
    workInProgress.child = newFiber;
    return;
  }
  // 处理元素节点
  if (typeof children === 'object' && children !== null) {
    const newFiber: FiberNode = {
      tag: typeof children.type === 'function' ? 'function_component' : 'host_component',
      type: children.type,
      key: children.key ?? null,
      pendingProps: children.props,
      memoizedProps: null,
      memoizedState: null,
      return: workInProgress,
      child: null,
      sibling: null,
      index: 0,
      alternate: null,
      flags: 'placement',
      deletions: null,
      lanes: 0,
      childLanes: 0,
    };
    workInProgress.child = newFiber;
  }
}

4. 函数组件与 Hooks 实现

React 18 的 Hook 调用顺序依赖 Fiber 节点上的 memoizedState 链表。以下是 useState 的精简实现:

代码语言:javascript
复制
type Hook = {
  memoizedState: any;
  queue: any[];
  next: Hook | null;
};

let currentlyRenderingFiber: FiberNode | null = null;
let hookIndex = 0;

function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,
    queue: [],
    next: null,
  };
  const fiber = currentlyRenderingFiber!;
  if (!fiber.memoizedState) {
    fiber.memoizedState = hook;
  } else {
    // 遍历到链表末尾
    let current = fiber.memoizedState;
    while (current.next) current = current.next;
    current.next = hook;
  }
  return hook;
}

export function useState<T>(initial: T): [T, (action: T | ((prev: T) => T)) => void] {
  const fiber = currentlyRenderingFiber!;
  const hook = mountWorkInProgressHook();
  
  // 读取 alternate 树中的历史状态
  const alternate = fiber.alternate;
  let state: T = initial;
  if (alternate && alternate.memoizedState) {
    // 真实场景需遍历到对应索引的 Hook
    state = (alternate.memoizedState as Hook).memoizedState ?? initial;
  }
  hook.memoizedState = state;
  
  const setState = (action: T | ((prev: T) => T)) => {
    const newState = typeof action === 'function' ? (action as (prev: T) => T)(state) : action;
    // 触发更新(重新调度渲染)
    // 实际实现需要标记 fiber 并触发 workLoop
    scheduleUpdate(fiber, newState);
  };
  
  return [state, setState];
}

// 在执行函数组件时设置当前 fiber
function renderFunctionComponent(fiber: FiberNode) {
  currentlyRenderingFiber = fiber;
  hookIndex = 0;
  const Component = fiber.type as Function;
  const props = fiber.pendingProps;
  const children = Component(props);
  fiber.memoizedProps = props;
  // 继续协调 children...
}

5. 提交(Commit)阶段

完成渲染后,将 workInProgress 树的变化应用到真实 DOM。React 18 支持 useLayoutEffectuseEffect 分别在提交前后执行。

代码语言:javascript
复制
function commitRoot(root: FiberNode) {
  // 1. 递归遍历 fiber 树,执行 DOM 操作
  commitWork(root.child);
  // 2. 将 workInProgress 树变为 current 树
  root.alternate = root;
  // 3. 执行 useEffect(在浏览器绘制后异步执行)
  scheduleEffectCallbacks(root);
}

function commitWork(fiber: FiberNode | null) {
  if (!fiber) return;
  // 找到最近的 host_component 父节点
  let parent = fiber.return;
  while (parent && parent.tag !== 'host_component') parent = parent.return;
  const domParent = (parent as any).domInstance;
  
  if (fiber.tag === 'host_component' || fiber.tag === 'host_text') {
    if (fiber.flags === 'placement') {
      // 创建 DOM 并插入
      const dom = createDOM(fiber);
      domParent.appendChild(dom);
    } else if (fiber.flags === 'update') {
      updateDOM(fiber);
    }
  }
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

6. 总结

我们仅用数百行代码便重现了 React 18 的核心架构:Fiber 链表使渲染可中断,MessageChannel + 时间切片实现并发调度,双缓冲树alternate指针保障状态一致性,Hooks通过链表顺序关联状态。

这个微型实现虽不包含事件合成、Suspense、并发优先级调度等完整功能,但它揭示了 React 18 最本质的设计哲学。建议读者运行代码,在浏览器 DevTools 的 Performance 面板中观察 workLoop 的切片效果,你会真正理解 React 如何做到“渲染不阻塞用户输入”。

React 18 的源码虽庞大,但核心思想高度凝练。掌握 Fiber 架构,你就掌握了现代前端框架设计的精髓。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • React 18 的并发渲染(Concurrent Rendering)是其最革命性的特性。它并非简单的新 API,而是一次架构级的重写——Fiber 架构赋予了 React 可中断、可恢复的渲染能力,使时间切片和优先级调度成为可能。本文将带你从零实现一个精简版 React 18,涵盖 Fiber 数据结构、协调(Reconciliation)、并发调度、双缓冲(Double Buffering) 和 Hooks 工作原理。
    • 1. Fiber 节点的数据结构
    • 2. 并发调度与时间切片
    • 3. 协调(Reconciliation)与 Diff
    • 4. 函数组件与 Hooks 实现
    • 5. 提交(Commit)阶段
    • 6. 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档