首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >硬核!卡颂同款:从0手写React18核心源码,彻底吃透Fiber与并发机制

硬核!卡颂同款:从0手写React18核心源码,彻底吃透Fiber与并发机制

原创
作者头像
用户12678265
发布2026-08-19 13:41:02
发布2026-08-19 13:41:02
150
举报

硬核!卡颂同款:从0手写React18核心源码,彻底吃透Fiber与并发机制

面试造火箭,工作拧螺丝?今天我们把火箭拆了重装。当别人还在背“Fiber 是链表、时间切片是分片”时,我们将用 200 行核心代码,从 createRootuseState,真实还原 React 18 的并发渲染(Concurrent Rendering)时间切片(Time Slicing)底层机制。这篇文章不教你怎么用 API,而是带你写一个能跑起来的“微型 React”。


一、为什么 React 18 必须重写渲染层?

React 15 的递归调和(Reconciliation)到 React 16+ 的 Fiber 架构,本质是为了解决 “渲染不可中断” 的致命伤。React 18 引入的并发特性(startTransitionuseDeferredValue)依赖更底层的 Lane 优先级模型

但剥开层层源码,核心只有三件事:

  1. Fiber 链表结构(支持中断恢复)
  2. 双缓冲树(current 与 workInProgress)
  3. 工作循环(WorkLoop) + 时间切片调度器(Scheduler)

我们从零开始,用原生 JS + 浏览器 API 实现这套机制。


二、定义 Fiber 节点数据结构

Fiber 的本质是一个 单向链表树,每个节点存有 child(子节点)、sibling(兄弟节点)、return(父节点)。我们将类型简化为 HostRoot(根)、HostComponent(原生 DOM)、FunctionComponent(函数组件)。

代码语言:javascript
复制
// 工作优先级(简化版)
const NoLane = 0;
const SyncLane = 1;   // 同步优先(类比 click 事件)
const DefaultLane = 2; // 普通优先级
const IdleLane = 3;   // 空闲执行

// Fiber 节点定义
let nextUnitOfWork = null;   // 当前待处理的 Fiber
let wipRoot = null;         // 进行中根 Fiber(workInProgress)
let currentRoot = null;     // 已提交的根 Fiber
let deletions = [];         // 待删除节点

function createFiber(tag, key, pendingProps) {
  return {
    tag,            // 'HostRoot' | 'HostComponent' | 'FunctionComponent'
    key,
    pendingProps,   // 新传入的 props
    memoizedState: null, // 对于函数组件,存 Hook 链表;对于 DOM 存节点
    updateQueue: null,   // 更新队列
    stateNode: null,     // 真实 DOM 或组件实例

    // 树结构指针
    return: null,
    child: null,
    sibling: null,

    // 双缓冲复用
    alternate: null, // 指向 current 树中的对应节点
    flags: 0,        // 副作用标记(Placement | Update | Deletion)
    lanes: NoLane,
    childLanes: NoLane,
  };
}

三、构建 Fiber 树 —— 从 JSX 到 Fiber 的转换

假设我们有以下 App 组件:

代码语言:javascript
复制
function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  );
}

createRoot 入口:

代码语言:javascript
复制
function createRoot(container) {
  // 根 Fiber 节点
  const rootFiber = createFiber('HostRoot', null, null);
  const root = {
    containerInfo: container,
    current: rootFiber,
  };
  rootFiber.stateNode = root;
  return { render: (element) => scheduleRoot(root, element) };
}

function scheduleRoot(root, element) {
  // 构建初始 Fiber 树
  const rootFiber = createFiber('HostRoot', null, null);
  rootFiber.stateNode = root;
  root.current = rootFiber;
  // 将 JSX 元素作为根 Fiber 的 pendingProps
  rootFiber.pendingProps = { children: element };
  // 开启工作循环
  wipRoot = rootFiber;
  currentRoot = null;
  nextUnitOfWork = wipRoot;
  requestIdleCallback(workLoop);
}

关键 reconcileChildren(调和子节点):我们这里采用 单链表 Diff(React 18 实际是多指针遍历,但核心逻辑一致)。

代码语言:javascript
复制
function reconcileChildren(currentFiber, newChildren) {
  if (!newChildren) return;
  // 简化:将 children 转为数组
  const children = Array.isArray(newChildren) ? newChildren : [newChildren];
  let prevSibling = null;
  let oldFiber = currentFiber.alternate ? currentFiber.alternate.child : null;

  for (let i = 0; i < children.length; i++) {
    const element = children[i];
    // 根据 element 创建新的 Fiber(此处忽略 key 复用逻辑,聚焦主流程)
    const newFiber = createFiber(
      typeof element.type === 'function' ? 'FunctionComponent' : 'HostComponent',
      element.key || null,
      { ...element.props, children: element.props?.children }
    );
    newFiber.return = currentFiber;
    
    if (i === 0) {
      currentFiber.child = newFiber;
    } else {
      prevSibling.sibling = newFiber;
    }
    prevSibling = newFiber;
  }
}

四、时间切片与 WorkLoop(React 18 核心)

React 18 的并发依靠 调度器(Scheduler),这里我们使用 MessageChannel + requestIdleCallback 模拟(真实场景用 MessageChannel 制造宏任务,避免 requestIdleCallback 的 50ms 限制)。

主循环:每次执行一个 Fiber 单元(performUnitOfWork),然后检查剩余时间。

代码语言:javascript
复制
const frameDeadline = 0;
let scheduledHostCallback = null;

function requestHostCallback(callback) {
  scheduledHostCallback = callback;
  if (typeof MessageChannel !== 'undefined') {
    const channel = new MessageChannel();
    channel.port1.onmessage = function() {
      const timeRemaining = () => frameDeadline - performance.now();
      if (scheduledHostCallback && scheduledHostCallback(timeRemaining) === true) {
        channel.port2.postMessage(null);
      }
    };
    channel.port2.postMessage(null);
  }
}

function workLoop(deadline) {
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    // 检查剩余时间,若小于 5ms 则让出主线程
    shouldYield = deadline.timeRemaining() < 5;
  }
  if (!nextUnitOfWork && wipRoot) {
    commitRoot(); // 进入提交阶段
  }
  // 若还有任务,继续申请下一次调度
  if (nextUnitOfWork) {
    requestHostCallback(workLoop);
  }
}

performUnitOfWork 负责执行当前 Fiber 并返回下一个待执行 Fiber(深度优先遍历):

代码语言:javascript
复制
function performUnitOfWork(fiber) {
  // beginWork:执行当前 Fiber,生成子 Fiber
  beginWork(fiber);
  
  if (fiber.child) {
    return fiber.child; // 优先遍历子节点
  }
  
  // 没有子节点,找兄弟节点
  let nextFiber = fiber;
  while (nextFiber) {
    completeUnitOfWork(nextFiber); // 完成当前节点(收集 DOM 操作)
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    nextFiber = nextFiber.return; // 向上回溯
  }
  return null;
}

beginWork 根据类型处理(核心 diff 逻辑):

代码语言:javascript
复制
function beginWork(fiber) {
  if (fiber.tag === 'HostRoot' || fiber.tag === 'HostComponent') {
    // 处理原生 DOM 或根节点
    const children = fiber.pendingProps?.children;
    reconcileChildren(fiber, children);
  } else if (fiber.tag === 'FunctionComponent') {
    // 执行函数组件,得到 JSX
    const hook = fiber.memoizedState;
    const children = fiber.type(fiber.pendingProps);
    reconcileChildren(fiber, children);
  }
}

completeUnitOfWork 负责创建 DOM 实例并挂载到 Fiber 上:

代码语言:javascript
复制
function completeUnitOfWork(fiber) {
  if (fiber.tag === 'HostComponent') {
    if (!fiber.stateNode) {
      // 创建 DOM 节点
      const dom = document.createElement(fiber.type);
      // 设置属性(简化)
      const props = fiber.pendingProps || {};
      Object.keys(props).forEach(key => {
        if (key !== 'children') dom[key] = props[key];
      });
      fiber.stateNode = dom;
    }
    // 将子 Fiber 的 DOM 挂载到当前节点下(便于提交)
    let child = fiber.child;
    while (child) {
      if (child.stateNode) {
        fiber.stateNode.appendChild(child.stateNode);
      }
      child = child.sibling;
    }
  }
}

五、提交阶段(Commit Phase)—— 双缓冲切换

nextUnitOfWork 为空时,表明整棵 Fiber 树构建完成,进入 Commit。React 18 的 Commit 分三步:before mutationmutationlayout。这里我们合并为关键两步:替换根 DOM + 更新 current 指针

代码语言:javascript
复制
function commitRoot() {
  // 将构建好的 wipRoot 的 DOM 挂载到真实容器
  const container = wipRoot.stateNode.containerInfo;
  const fiberRoot = wipRoot;
  
  // 获取根 Fiber 下的第一个子节点 DOM
  if (fiberRoot.child) {
    const dom = fiberRoot.child.stateNode;
    container.innerHTML = ''; // 清空容器(生产环境优化)
    container.appendChild(dom);
  }
  
  // 双缓冲切换:currentRoot 指向旧树,wipRoot 变为 current
  currentRoot = wipRoot;
  wipRoot = null;
}

六、Hooks 原理 —— 链表实现(useState / useEffect)

Hooks 的执行依赖 当前 Fiber 上的 Hook 链表。我们以 useState 为例,完整还原 调度更新 机制。

代码语言:javascript
复制
let currentlyRenderingFiber = null;
let workInProgressHook = null;

function renderWithHooks(fiber) {
  currentlyRenderingFiber = fiber;
  fiber.memoizedState = null; // 重置 Hook 链表
  workInProgressHook = null;
  // 执行函数组件(内部触发 useState 等)
  const children = fiber.type(fiber.pendingProps);
  currentlyRenderingFiber = null;
  return children;
}

function useState(initial) {
  // 获取当前正在执行的 Fiber
  const fiber = currentlyRenderingFiber;
  
  // 获取当前 Hook 或创建新 Hook
  let hook;
  if (!workInProgressHook) {
    // 第一个 Hook
    hook = {
      memoizedState: initial,
      next: null,
      queue: [], // 更新队列
    };
    fiber.memoizedState = hook;
    workInProgressHook = hook;
  } else {
    // 复用 alternate 树中的旧 Hook
    const alternateHook = fiber.alternate?.memoizedState;
    // 遍历找到对应的 hook(这里简化单链表取 next)
    if (workInProgressHook.next) {
      hook = workInProgressHook.next;
    } else {
      hook = {
        memoizedState: alternateHook?.memoizedState || initial,
        next: null,
        queue: [],
      };
      workInProgressHook.next = hook;
    }
    workInProgressHook = hook;
  }

  // 执行队列中的更新(计算新 state)
  let newState = hook.memoizedState;
  while (hook.queue.length) {
    const action = hook.queue.shift();
    newState = typeof action === 'function' ? action(newState) : action;
  }
  hook.memoizedState = newState;

  // 返回 [state, setState]
  const setState = (action) => {
    // 将更新加入队列
    hook.queue.push(action);
    // 触发一次新的调度(重新渲染)
    scheduleUpdate(fiber);
  };
  return [newState, setState];
}

function scheduleUpdate(fiber) {
  // 从当前 Fiber 向上回溯,设置 wipRoot
  wipRoot = fiber;
  // 构建 alternate(复用 current 树结构)
  wipRoot.alternate = currentRoot;
  nextUnitOfWork = wipRoot;
  requestIdleCallback(workLoop);
}

useEffect 简易实现(副作用收集与执行):

代码语言:javascript
复制
function useEffect(callback, deps) {
  const fiber = currentlyRenderingFiber;
  const hook = mountWorkInProgressHook(); // 同上链表逻辑
  const prevDeps = hook.memoizedState?.deps;
  
  // 比较依赖是否变化(浅比较)
  const hasChanged = !prevDeps || deps.some((dep, i) => dep !== prevDeps[i]);
  if (hasChanged) {
    hook.memoizedState = { deps, callback };
    // 标记副作用(在 commit 阶段执行)
    fiber.flags |= 1; // PassiveEffect
  }
}

七、实现并发紧急任务插队(Lane 模型简化)

React 18 支持 startTransition,本质是标记低优先级更新。我们通过 优先级队列 模拟:

代码语言:javascript
复制
let rootLanes = 0;

function scheduleUpdate(fiber, lane = DefaultLane) {
  // 标记 Fiber 及其祖先的 lanes
  let node = fiber;
  while (node) {
    node.lanes |= lane;
    node = node.return;
  }
  // 根节点累加
  if (wipRoot) {
    wipRoot.lanes |= lane;
  }
  // 若遇到 SyncLane,立即执行(不切片)
  if (lane === SyncLane) {
    performSyncWork();
  } else {
    requestIdleCallback(workLoop);
  }
}

function performSyncWork() {
  // 同步执行所有 Fiber,不检查时间片
  while (nextUnitOfWork) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  }
  if (wipRoot) commitRoot();
}

workLoop 中,我们优先处理高 Lane 的节点:

代码语言:javascript
复制
function getNextLane(root) {
  if (root.lanes & SyncLane) return SyncLane;
  if (root.lanes & DefaultLane) return DefaultLane;
  return IdleLane;
}

八、跑通完整 Demo

将上述代码整合,运行:

代码语言:javascript
复制
<body>
  <div id="root"></div>
  <script>
    // 定义 App 组件(全局)
    function App() {
      const [count, setCount] = useState(0);
      return {
        type: 'div',
        props: {
          children: [
            { type: 'h1', props: { children: count } },
            { type: 'button', props: { onClick: () => setCount(c => c + 1), children: '+1' } }
          ]
        }
      };
    }
    
    const root = createRoot(document.getElementById('root'));
    root.render({ type: App, props: {} });
  </script>
</body>

点击按钮时,setCount 触发 scheduleUpdate,浏览器在空闲时间执行 workLoop,界面无卡顿更新——这就是时间切片赋予 React 18 的“并发”超能力


九、源码级避坑与性能调优

真实 React 18 特性

我们的简化版实现要点

双缓冲复用

通过 alternate 复用旧 Fiber,我们已在 useState 中实现 alternateHook 取值

Batched Updates(批量更新)

真实源码通过 executionContext 控制,我们可模拟 isBatching 标志,将多个 setState 合并到一次调度

Suspense & 流式 SSR

需在 Fiber 中增加 Suspense 类型,并暂停 completeUnitOfWork,属于上层扩展

原生事件系统

真实 React 采用合成事件,我们直接绑定 onClick 到 DOM,不影响主逻辑理解

性能关键:在 reconcileChildren 中,如果直接 innerHTML='' 会丢失内部状态。生产级应通过 flags 标记 精准增删改(PlacementUpdateDeletion)。优化提示:在 completeUnitOfWork 中,若 fiber.flags & Placement 才执行 appendChild


十、结语:从手写源码到架构思维

我们用不到 300 行代码,真实模拟了 React 18 从 JSX 解析 → Fiber 构建 → 时间切片调度 → 双缓冲提交 → Hooks 更新驱动 的全链路。

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

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

目录
  • 硬核!卡颂同款:从0手写React18核心源码,彻底吃透Fiber与并发机制
    • 一、为什么 React 18 必须重写渲染层?
    • 二、定义 Fiber 节点数据结构
    • 三、构建 Fiber 树 —— 从 JSX 到 Fiber 的转换
    • 四、时间切片与 WorkLoop(React 18 核心)
    • 五、提交阶段(Commit Phase)—— 双缓冲切换
    • 六、Hooks 原理 —— 链表实现(useState / useEffect)
    • 七、实现并发紧急任务插队(Lane 模型简化)
    • 八、跑通完整 Demo
    • 九、源码级避坑与性能调优
    • 十、结语:从手写源码到架构思维
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档