首页  |  学习总览  |  ← 返回专题总览 进阶专题 09 · 源码解析

React 源码深度解析 🔥 重点

从 Fiber 架构到 Hooks 原理,从 Scheduler 调度到 Concurrent Mode,彻底搞懂 React 运行机制
学习时长:约 18 小时 | 前置:阶段 05 框架核心 | 产出:手写 mini-React + 源码阅读笔记

一、React 架构总览

React 的三层架构

React(元素/组件)
Reconciler(协调器)
Renderer(渲染器)
核心思想:React 16 引入 Fiber 架构,将同步渲染改为可中断的异步渲染,解决了大型应用"卡死"的问题。

二、Fiber 架构原理

2.1 什么是 Fiber?

Fiber 是一个链表结构的工作单元,每个 React 元素对应一个 Fiber 节点:

// Fiber 节点核心结构(简化版)
function FiberNode(tag, pendingProps, key) {
  // 实例属性
  this.tag = tag;           // 组件类型(Function/Class/Host)
  this.key = key;
  this.type = null;         // 元素类型(div/span/函数组件)
  this.stateNode = null;   // 真实 DOM 或组件实例
  
  // 链表树结构
  this.return = null;       // 父 Fiber
  this.child = null;        // 子 Fiber
  this.sibling = null;      // 兄弟 Fiber
  
  // 工作相关
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.memoizedState = null; // Hooks 链表
  this.updateQueue = null;   // 更新队列
  
  // 副作用
  this.flags = NoFlags;     // 操作标记(Placement/Update/Delete)
  this.subtreeFlags = NoFlags;
  this.deletions = null;
}

2.2 Fiber 树遍历算法

Fiber 使用深度优先遍历(DFS),遵循 child → sibling → return 的顺序:

// 简化的 Fiber 遍历过程
function performUnitOfWork(unitOfWork) {
  // 1. beginWork:处理当前节点,返回子节点
  const next = beginWork(current, unitOfWork);
  
  if (next !== null) {
    // 有子节点,继续向下遍历
    workInProgress = next;
  } else {
    // 2. completeWork:当前节点完成,开始回溯
    completeUnitOfWork(unitOfWork);
  }
}

function completeUnitOfWork(unitOfWork) {
  let completedWork = unitOfWork;
  do {
    // 完成当前节点
    completeWork(completedWork);
    // 检查是否有兄弟节点
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber; // 转向兄弟节点
      return;
    }
    // 没有兄弟,返回父节点
    completedWork = completedWork.return;
    workInProgress = completedWork;
  } while (completedWork !== null);
}

遍历示例

对于这样的组件树:

      App
     /    \
   Header  Content
          /      \
        Sidebar   Main

遍历顺序:App → Header → Content → Sidebar → Main

每个节点会经历 beginWork(向下)completeWork(向上) 两个阶段

三、Reconciler 协调流程

3.1 双缓冲机制(Double Buffering)

React 维护两棵 Fiber 树:

双缓冲的好处:在内存中构建新树,构建完成后一次性切换,避免出现"半更新"的中间状态。

3.2 Diff 算法核心策略

React 的 Diff 算法基于三个假设进行优化:

  1. 元素类型不同 → 直接替换(div 变 span,不复用)
  2. key 作为标识(列表项通过 key 判断是否可以复用)
  3. 只比较同层级(不跨层级比较,降低复杂度 O(n))

3.3 单节点 Diff

// 单节点复用判断逻辑
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
  const key = element.key;
  let child = currentFirstChild;
  
  while (child !== null) {
    if (child.key === key) {
      // key 相同
      if (child.type === element.type) {
        // type 也相同 → 复用
        const existing = useFiber(child, element.props);
        existing.return = returnFiber;
        return existing;
      } else {
        // type 不同 → 删除旧节点
        deleteChild(returnFiber, child);
        break;
      }
    } else {
      // key 不同 → 删除
      deleteChild(returnFiber, child);
    }
    child = child.sibling;
  }
  // 新建节点
  const created = createFiberFromElement(element);
  created.return = returnFiber;
  return created;
}

3.4 多节点 Diff(列表)

列表 Diff 分三种情况处理:

1. 节点更新

key 和 type 都相同 → 复用节点,更新 props

2. 节点新增

新节点在旧节点中不存在 → 创建新 Fiber

3. 节点删除

旧节点在新节点中不存在 → 标记删除

4. 节点移动

key 存在但位置变化 → 通过 lastPlacedIndex 判断移动

四、Hooks 实现原理

4.1 Hooks 链表结构

函数组件的 Hooks 以单向链表形式存储在 Fiber 节点的 memoizedState 上:

// Hook 对象结构
const hook = {
  memoizedState: null,   // 当前状态值(state/effect 等)
  baseState: null,       // 基础状态
  baseQueue: null,       // 基础更新队列
  queue: null,           // 更新队列(UpdateQueue)
  next: null,             // 下一个 Hook
};

// 更新队列结构(环形链表)
const queue = {
  pending: null,         // 待处理的更新(环形链表尾)
  dispatch: null,        // setState 函数
  lastRenderedState: null // 上次渲染的状态
};

4.2 useState 实现

// useState 本质是 useReducer 的语法糖
function useState(initialState) {
  const hook = mountWorkInProgressHook();
  
  if (isMount) {
    // 首次渲染
    hook.memoizedState = 
      hook.baseState = 
        typeof initialState === 'function' 
          ? initialState() : initialState;
  } else {
    // 更新渲染
    hook.memoizedState = hook.baseState;
  }
  
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedState: hook.memoizedState,
  });
  
  const dispatch = (queue.dispatch = dispatchSetState.bind(
    null, currentlyRenderingFiber, queue
  ));
  
  return [hook.memoizedState, dispatch];
}

// dispatch 触发更新
function dispatchSetState(fiber, queue, action) {
  // 1. 创建 update 对象
  const update = {
    lane: requestUpdateLane(),
    action,
    hasEagerState: false,
    eagerState: null,
    next: null,
  };
  
  // 2. 加入更新队列(环形链表)
  const pending = queue.pending;
  if (pending === null) {
    update.next = update; // 指向自己,形成环
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
  
  // 3. 调度更新
  scheduleUpdateOnFiber(fiber, lane);
}

4.3 useEffect 实现

// useEffect 的 memoizedState 存储的是 effect 对象
function mountEffect(create, deps) {
  return mountEffectImpl(
    PassiveEffect | HookPassive,
    HookPassive,
    create,
    deps
  );
}

function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  
  currentlyRenderingFiber.flags |= fiberFlags;
  
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    undefined,
    nextDeps
  );
}

// effect 对象结构
function pushEffect(hookFlags, create, destroy, deps) {
  const effect = {
    tag: hookFlags,     // 标记是否需要执行
    create,             // 回调函数
    destroy,            // 清理函数
    deps,               // 依赖数组
    next: null,         // 下一个 effect
  };
  // 添加到 fiber.updateQueue.lastEffect 环形链表
  const componentUpdateQueue = currentlyRenderingFiber.updateQueue;
  if (componentUpdateQueue === null) {
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    currentlyRenderingFiber.updateQueue = componentUpdateQueue;
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}
注意:useEffect 的依赖比较是 Object.is,对于对象和数组每次都是新引用,所以要用 useMemo/useCallback 来稳定引用。

4.4 useRef 实现

// useRef 是最简单的 Hook,memoizedState 存储 { current: initialValue }
function mountRef(initialValue) {
  const hook = mountWorkInProgressHook();
  const ref = { current: initialValue };
  hook.memoizedState = ref;
  return ref;
}

// useRef 返回的 ref 对象在整个组件生命周期中保持同一个引用
// 所以它常用于:
// 1. 访问 DOM 元素
// 2. 保存可变值(不触发重渲染)
// 3. 保存定时器 ID 等

五、Scheduler 调度器

5.1 时间切片(Time Slicing)

React 18 的 Concurrent Mode 通过时间切片实现可中断渲染:

// 简化的 workLoop 实现
function workLoopConcurrent() {
  // 有工作且未超时,继续执行
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

// 每 5ms 检查一次是否需要让出主线程
function shouldYield() {
  const currentTime = getCurrentTime();
  if (currentTime >= deadline) {
    // 时间片用完,让出主线程
    if ((currentTime - lastYieldyStartTime) >= 5ms) {
      return true;
    }
  }
  return false;
}
时间切片原理:浏览器每帧 16.6ms,React 每 5ms 检查一次,确保有足够时间处理用户输入和动画。

5.2 Lane 优先级模型

React 17+ 使用 Lane(车道)模型管理更新优先级:

// Lane 使用 31 位二进制表示优先级
// 数值越小,优先级越高
export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;
export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lanes = /*            */ 0b0000000000000000000000000000100;
export const DefaultHydrationLane: Lane = /*          */ 0b0000000000000000000000000001000;
export const DefaultLane: Lanes = /*                    */ 0b0000000000000000000000000010000;
export const TransitionHydrationLane: Lane = /*       */ 0b0000000000000000000000000100000;
export const TransitionLanes: Lanes = /*               */ 0b0000000000000000000011111000000;
export const RetryLanes: Lanes = /*                     */ 0b0000000000000000011100000000000;
export const SelectiveHydrationLane: Lane = /*         */ 0b0000000000000000100000000000000;
export const IdleHydrationLane: Lane = /*              */ 0b0000000000000001000000000000000;
export const IdleLane: Lanes = /*                        */ 0b0000000000000010000000000000000;
export const OffscreenLane: Lane = /*                 */ 0b0000000000000100000000000000000;

优先级分类

优先级场景示例
SyncLane同步、最高优先级legacy 模式
InputContinuousLane用户输入滚动、点击
DefaultLane默认网络请求回调
TransitionLane过渡动画startTransition
IdleLane空闲时离屏内容

六、Concurrent Mode 并发模式

6.1 并发特性

6.2 startTransition 原理

// startTransition 将回调中的更新标记为 TransitionLane
function startTransition(scope) {
  const prevTransition = ReactCurrentBatchConfig.transition;
  ReactCurrentBatchConfig.transition = {};
  
  try {
    scope(); // 执行回调,其中的 setState 会被标记为 TransitionLane
  } finally {
    ReactCurrentBatchConfig.transition = prevTransition;
  }
}

// 使用示例
function App() {
  const [input, setInput] = useState('');
  const [list, setList] = useState([]);
  
  const handleChange = (e) => {
    setInput(e.target.value); // 高优先级,立即更新
    startTransition(() => {
      setList(generateList(e.target.value)); // 低优先级,可中断
    });
  };
}

6.3 Suspense 与 useTransition 配合

// 使用 useTransition 避免 Suspense 闪烁
function App() {
  const [tab, setTab] = useState('home');
  const [isPending, startTransition] = useTransition();
  
  return (
    <>
      <button onClick={() => setTab('home')}>Home</button>
      <button onClick={() => {
        startTransition(() => setTab('posts'));
      }}>Posts</button>
      
      {isPending && <Spinner />}
      <Suspense fallback={<Loading />}>
        <TabContent tab={tab} />
      </Suspense>
    </>
  );
}

七、手写 mini-React 实战

7.1 实现 createElement

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === 'object' ? child : createTextElement(child)
      ),
    },
  };
}

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

7.2 实现 render 函数

function render(element, container) {
  const dom =
    element.type === "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(element.type);

  const isProperty = (key) => key !== "children";
  Object.keys(element.props)
    .filter(isProperty)
    .forEach((name) => {
      dom[name] = element.props[name];
    });

  element.props.children.forEach((child) => render(child, dom));
  container.appendChild(dom);
}

7.3 实现 Fiber 架构(可中断渲染)

let nextUnitOfWork = null;
let currentRoot = null;
let wipRoot = null;
let deletions = null;

function workLoop(deadline) {
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1;
  }

  if (!nextUnitOfWork && wipRoot) {
    commitRoot();
  }

  requestIdleCallback(workLoop);
}

requestIdleCallback(workLoop);

function performUnitOfWork(fiber) {
  if (typeof fiber.type === 'function') {
    updateFunctionComponent(fiber);
  } else {
    updateHostComponent(fiber);
  }

  if (fiber.child) return fiber.child;
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) return nextFiber.sibling;
    nextFiber = nextFiber.return;
  }
}

八、面试高频问题

Q1: React Fiber 解决了什么问题?
查看答案 ▼
React 15 的 Stack Reconciler 是同步递归的,一旦开始渲染就无法中断。对于大型应用,长时间占用主线程会导致动画掉帧、用户输入无响应。

Fiber 架构将渲染任务拆分成多个小单元,每执行完一个单元就检查是否需要让出主线程,实现了可中断的异步渲染
Q2: 为什么 Hooks 不能在条件语句中使用?
查看答案 ▼
Hooks 以链表形式存储在 Fiber 节点的 memoizedState 上,调用顺序必须一致。

如果条件语句导致某个 Hook 有时执行有时不执行,链表指针错位,后续 Hook 读取到错误的状态值。
Q3: React 18 的 Automatic Batching 是什么?
查看答案 ▼
React 17 只在事件处理函数中自动批量更新,Promise/setTimeout/原生事件中的多次 setState 会触发多次渲染。

React 18 引入了 Automatic Batching,无论更新来自哪里,都会自动合并成一次渲染,减少不必要的重渲染。
Q4: useLayoutEffect 和 useEffect 的区别?
查看答案 ▼
  • useLayoutEffect:在浏览器绘制前同步执行,可以读取 DOM 布局信息并同步修改,适合做 DOM 测量和同步调整
  • useEffect:在浏览器绘制后异步执行,不会阻塞页面渲染,适合数据请求、订阅等副作用