React源码解读(一)

13 阅读31分钟

React源码解读(一)

相关资料

相关问题

React 中, "setState"是同步还是异步?

在 React 18 之前,setState 的行为取决于调用的上下文环境。而在 React 18 引入自动批处理(Automatic Batching)后,行为更加统一。

React 18 之前的行为:

  • 在 React 事件处理函数中:异步批量更新
  • 在 setTimeout、Promise、原生事件等场景中:同步更新

React 18 的行为:

在所有场景下都默认进行批量更新(异步),无论是在事件处理函数、setTimeout 还是 Promise 中。

// React 18 之前的行为
class Example extends React.Component {
  state = { count: 0 };

  handleClick = () => {
    // 异步批量更新
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count); // 输出 0,而不是 1
  };

  handleTimeout = () => {
    setTimeout(() => {
      // React 18 之前:同步更新
      this.setState({ count: this.state.count + 1 });
      console.log(this.state.count); // React 18 之前输出 1
    }, 0);
  };
}

// React 18 的行为
function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    // 批量更新
    setCount(c => c + 1);
    setCount(c => c + 1);
    // 只会触发一次重新渲染
  };

  const handleAsync = () => {
    setTimeout(() => {
      // React 18:也会批量更新
      setCount(c => c + 1);
      setCount(c => c + 1);
      // 只触发一次重新渲染
    }, 0);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>同步点击</button>
      <button onClick={handleAsync}>异步点击</button>
    </div>
  );
}

批处理流程:

graph TD
    A["触发 setState"] --> B{"是否在批处理上下文"}
    B -->|是| C["将更新加入队列"]
    B -->|否 React 18前| D["立即执行更新"]
    B -->|否 React 18| C
    C --> E["等待当前执行栈清空"]
    E --> F["合并所有更新"]
    F --> G["执行一次重新渲染"]
    D --> G

如何强制同步更新(React 18):

import { flushSync } from 'react-dom';

function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    flushSync(() => {
      setCount(c => c + 1);
    });
    // 这里 count 已经更新
    console.log(count); // 输出最新值
  };

  return <button onClick={handleClick}>强制同步</button>;
}

React 中key的作用是什么?

key 是 React 用于识别哪些元素发生了变化、添加或删除的特殊属性。它在 Diff 算法中起着关键作用,帮助 React 优化渲染性能。

key 的核心作用:

  1. 唯一标识元素:在同一层级的兄弟节点中唯一标识每个元素
  2. 优化 Diff 算法:帮助 React 快速定位变化的节点
  3. 保持组件状态:key 不变时,组件实例会被复用,状态得以保留
  4. 触发重新挂载:key 变化时,旧组件卸载,新组件挂载
// 不使用 key 的问题示例
function BadList() {
  const [items, setItems] = useState(['A', 'B', 'C']);

  const addItem = () => {
    setItems(['X', ...items]); // 在开头插入新元素
  };

  return (
    <div>
      {/* 没有 key,React 会认为第一个元素从 A 变成了 X,第二个从 B 变成了 A... */}
      {items.map(item => (
        <input defaultValue={item} />
      ))}
      <button onClick={addItem}>添加</button>
    </div>
  );
}

// 正确使用 key
function GoodList() {
  const [items, setItems] = useState([
    { id: 1, name: 'A' },
    { id: 2, name: 'B' },
    { id: 3, name: 'C' }
  ]);

  const addItem = () => {
    setItems([{ id: Date.now(), name: 'X' }, ...items]);
  };

  return (
    <div>
      {/* 使用 key,React 能正确识别新插入的元素 */}
      {items.map(item => (
        <input key={item.id} defaultValue={item.name} />
      ))}
      <button onClick={addItem}>添加</button>
    </div>
  );
}

Diff 算法中 key 的作用流程:

graph LR
    A["开始 Diff"] --> B{"是否有 key"}
    B -->|有 key| C["通过 key 查找旧节点"]
    B -->|无 key| D["按位置顺序比较"]
    C --> E{"找到相同 key"}
    E -->|是| F["复用节点,更新属性"]
    E -->|否| G["创建新节点"]
    D --> H["逐个比较类型"]
    H --> I{"类型相同"}
    I -->|是| J["复用节点"]
    I -->|否| K["删除旧节点,创建新节点"]

key 的最佳实践:

// ❌ 不要使用索引作为 key(除非列表永远不会重新排序)
items.map((item, index) => <div key={index}>{item}</div>);

// ✅ 使用稳定的唯一标识
items.map(item => <div key={item.id}>{item.name}</div>);

// ✅ 使用 key 强制重新挂载组件
function UserProfile({ userId }) {
  // 当 userId 变化时,整个组件重新挂载,状态重置
  return (
    <div key={userId}>
      <UserForm userId={userId} />
    </div>
  );
}

// ✅ 列表过滤后保持 key 稳定
function FilteredList({ items, filter }) {
  const filtered = items.filter(item => item.category === filter);
  
  return (
    <ul>
      {filtered.map(item => (
        // 使用原始数据的 id,而不是过滤后的索引
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

key 对性能的影响对比:

// 场景:在列表开头插入新元素

// 没有 key:React 会更新所有元素
// [A, B, C] -> [X, A, B, C]
// 操作:更新 4 个元素(X←A, A←B, B←C, C←新建)

// 有 key:React 只需插入新元素
// [{id:1, A}, {id:2, B}, {id:3, C}] -> [{id:4, X}, {id:1, A}, {id:2, B}, {id:3, C}]
// 操作:插入 1 个新元素(X),其他复用

React 工程架构

React 采用 Monorepo 架构管理多个包,核心包之间职责分明、相互协作。整体架构遵循"分层解耦"的设计原则。

graph TB
    A["React 应用层"] --> B["react 核心包"]
    B --> C["react-reconciler 协调器"]
    C --> D["scheduler 调度器"]
    C --> E["react-dom 渲染器"]
    C --> F["react-native 渲染器"]
    C --> G["react-noop-renderer 测试渲染器"]
    
    style B fill:#61dafb
    style C fill:#ffd700
    style D fill:#ff6b6b
    style E fill:#51cf66

react

react 包是 React 的核心 API 层,提供了开发者直接使用的组件定义、Hooks 等基础能力,但不包含具体的渲染和调度逻辑。

核心职责:

  • 定义组件基础类(Component、PureComponent)
  • 提供 Hooks API(useState、useEffect 等)
  • 创建 React 元素(createElement、JSX)
  • 提供 Context API

核心 API 示例:

// 1. 组件定义
import { Component, PureComponent } from 'react';

class MyComponent extends Component {
  render() {
    return <div>{this.props.children}</div>;
  }
}

// 2. Hooks API
import { useState, useEffect, useMemo, useCallback } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
  
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// 3. createElement(JSX 编译后的实际调用)
import { createElement } from 'react';

// JSX: <div className="container">Hello</div>
// 编译后:
const element = createElement(
  'div',
  { className: 'container' },
  'Hello'
);

// 4. Context API
import { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Button</button>;
}

react 包的架构定位:

react 包只负责定义 API 接口和数据结构,不关心如何调度、如何渲染。这种设计使得 React 可以支持多种渲染目标(DOM、Native、Canvas 等)。

react-dom

react-dom 是 React 的 DOM 渲染器,负责将 React 元素渲染到浏览器 DOM 中,并处理 DOM 事件系统。

核心职责:

  • 将虚拟 DOM 渲染到真实 DOM
  • 管理 DOM 事件系统(事件委托、合成事件)
  • 提供 Hydration(服务端渲染的客户端激活)
  • 提供 Portals(跨层级渲染)

核心 API 示例:

// 1. 渲染到 DOM (React 18+)
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

// 2. 服务端渲染的 Hydration
import { hydrateRoot } from 'react-dom/client';

hydrateRoot(document.getElementById('root'), <App />);

// 3. Portals - 将子节点渲染到不同的 DOM 节点
import { createPortal } from 'react-dom';

function Modal({ children }) {
  return createPortal(
    <div className="modal">{children}</div>,
    document.getElementById('modal-root')
  );
}

// 4. flushSync - 强制同步更新
import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() => {
    setCount(c => c + 1);
  });
  // DOM 已同步更新
}

事件系统架构:

sequenceDiagram
    participant User as 用户操作
    participant DOM as DOM 节点
    participant Root as 根节点监听器
    participant Synthetic as 合成事件系统
    participant Handler as React 事件处理器
    
    User->>DOM: 点击按钮
    DOM->>Root: 事件冒泡到根节点
    Root->>Synthetic: 捕获原生事件
    Synthetic->>Synthetic: 创建合成事件对象
    Synthetic->>Handler: 分发到对应组件
    Handler->>Handler: 执行 onClick 等处理器

react-reconciler

react-reconciler 是 React 的协调器(核心引擎),负责管理整个更新流程,包括 Fiber 架构、Diff 算法、调度协调等。

核心职责:

  • 构建和管理 Fiber 树
  • 执行 Diff 算法(协调过程)
  • 管理组件生命周期
  • 协调调度器(Scheduler)和渲染器(Renderer)

Fiber 架构:

// Fiber 节点的简化结构
const fiber = {
  // 节点类型和标识
  tag: WorkTag,              // 标识 Fiber 类型(函数组件、类组件等)
  key: string | null,        // 用于 Diff 的 key
  elementType: any,          // React 元素类型
  type: any,                 // 组件类型(函数、类等)
  
  // Fiber 树结构
  return: Fiber | null,      // 父 Fiber
  child: Fiber | null,       // 第一个子 Fiber
  sibling: Fiber | null,     // 下一个兄弟 Fiber
  index: number,             // 在父节点中的索引
  
  // 数据和状态
  pendingProps: any,         // 新的 props
  memoizedProps: any,        // 上一次的 props
  memoizedState: any,        // 上一次的 state
  updateQueue: UpdateQueue,  // 更新队列
  
  // 副作用
  flags: Flags,              // 副作用标记(增删改等)
  subtreeFlags: Flags,       // 子树副作用标记
  deletions: Array<Fiber>,   // 需要删除的子节点
  
  // 双缓存机制
  alternate: Fiber | null,   // 指向另一棵树的对应节点
};

双缓存机制:

graph LR
    A["current Fiber 树<br/>当前显示"] --> B["workInProgress Fiber 树<br/>正在构建"]
    B --> C["完成构建"]
    C --> D["切换指针"]
    D --> E["workInProgress 成为 current"]
    E --> A
    
    style A fill:#51cf66
    style B fill:#ffd700
    style E fill:#51cf66

协调过程(Reconciliation):

// 简化的协调流程示例
function workLoop(deadline) {
  // 循环处理 Fiber 节点
  while (workInProgress !== null && !shouldYield(deadline)) {
    workInProgress = performUnitOfWork(workInProgress);
  }
  
  // 如果还有工作未完成,继续调度
  if (workInProgress !== null) {
    requestIdleCallback(workLoop);
  } else {
    // 工作完成,提交更新
    commitRoot();
  }
}

function performUnitOfWork(fiber) {
  // 1. beginWork: 处理当前 Fiber
  const next = beginWork(fiber);
  
  if (next !== null) {
    // 有子节点,返回子节点
    return next;
  }
  
  // 2. completeWork: 没有子节点,完成当前节点
  let completedWork = fiber;
  while (completedWork !== null) {
    completeUnitOfWork(completedWork);
    
    // 如果有兄弟节点,返回兄弟节点
    if (completedWork.sibling !== null) {
      return completedWork.sibling;
    }
    
    // 回到父节点
    completedWork = completedWork.return;
  }
  
  return null;
}

scheduler

scheduler 是 React 的调度器,负责任务的优先级调度和时间切片,实现可中断的异步渲染。

核心职责:

  • 任务优先级管理
  • 时间切片(Time Slicing)
  • 任务调度和执行
  • 使用最小堆管理任务队列

优先级系统:

// React 的优先级级别(从高到低)
const ImmediatePriority = 1;      // 立即执行(如用户输入)
const UserBlockingPriority = 2;   // 用户交互(如点击、滚动)
const NormalPriority = 3;         // 普通优先级(如数据请求)
const LowPriority = 4;            // 低优先级(如分析上报)
const IdlePriority = 5;           // 空闲时执行(如预加载)

// 调度任务示例
import { 
  unstable_scheduleCallback as scheduleCallback,
  unstable_NormalPriority as NormalPriority,
  unstable_UserBlockingPriority as UserBlockingPriority
} from 'scheduler';

// 高优先级任务(用户交互)
scheduleCallback(UserBlockingPriority, () => {
  updateUserInput();
});

// 普通优先级任务(数据更新)
scheduleCallback(NormalPriority, () => {
  fetchAndUpdateData();
});

时间切片机制:

gantt
    title 时间切片渲染流程
    dateFormat X
    axisFormat %L
    
    section 浏览器帧
    执行 JS: 0, 5
    渲染: 5, 8
    空闲: 8, 16
    
    section 下一帧
    执行 JS: 16, 21
    渲染: 21, 24
    空闲: 24, 32
    
    section React 任务
    任务片段1: 0, 5
    任务片段2: 16, 21
    任务片段3: 32, 37

调度器工作流程:

// 简化的调度器实现
class Scheduler {
  constructor() {
    this.taskQueue = new MinHeap(); // 最小堆存储任务
    this.isScheduling = false;
  }
  
  // 调度任务
  scheduleTask(priority, callback) {
    const task = {
      id: taskIdCounter++,
      callback,
      priorityLevel: priority,
      expirationTime: getCurrentTime() + priorityToTimeout(priority),
    };
    
    this.taskQueue.push(task);
    
    if (!this.isScheduling) {
      this.isScheduling = true;
      this.requestHostCallback(this.flushWork);
    }
  }
  
  // 执行任务
  flushWork(deadline) {
    while (this.taskQueue.peek() && !shouldYieldToHost(deadline)) {
      const task = this.taskQueue.pop();
      const callback = task.callback;
      
      const continuationCallback = callback();
      
      // 如果任务返回了继续执行的函数,重新加入队列
      if (typeof continuationCallback === 'function') {
        task.callback = continuationCallback;
        this.taskQueue.push(task);
      }
    }
    
    // 还有任务未完成,继续调度
    if (this.taskQueue.peek()) {
      return true;
    }
    
    this.isScheduling = false;
    return false;
  }
}

// 判断是否需要让出控制权
function shouldYieldToHost(deadline) {
  const timeElapsed = getCurrentTime() - deadline.startTime;
  return timeElapsed >= 5; // 5ms 时间片
}
Lane 模型与调度链路

Lane 是 React 18 中引入的优先级抽象,使用位掩码对任务进行分类。一次更新可以占用多个 Lane,而根节点会记录所有子树传播上来的 childLanes,从而在顶层快速检索出最高优先级的待办任务。

function ensureRootIsScheduled(root) {
  const existingCallback = root.callbackNode;
  const nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
  if (nextLanes === NoLanes) {
    // 没有工作需要调度,取消现有回调
    if (existingCallback !== null) {
      cancelCallback(existingCallback);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  }

  const newCallbackPriority = getHighestPriorityLane(nextLanes);
  const newCallbackNode = scheduleCallback(
    lanePriorityToSchedulerPriority(newCallbackPriority),
    performConcurrentWorkOnRoot.bind(null, root)
  );

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}
graph TD
    A["组件触发更新<br/>scheduleUpdateOnFiber"] --> B["合并 lane<br/>markRootUpdated"]
    B --> C["getNextLanes"]
    C --> D["ensureRootIsScheduled"]
    D --> E["scheduleCallback"]
    E --> F["performConcurrentWorkOnRoot"]
    F --> G["workLoopConcurrent"]
    G --> H["是否需要让出控制权?"]
    H -->|是| I["shouldYieldToHost"]
    H -->|否| J["renderRootConcurrent"]
自动批处理与批量提交

React 18 默认开启自动批处理:只要处于同一个事件循环 tick 内触发的更新,都会在 Render 阶段合并,再在 Commit 阶段一次性提交。这依赖 flushSyncCallbacksbatchedUpdates 对调度入口的包装。

function dispatchSetState( fiber, queue, action ) {
  const lane = requestUpdateLane(fiber);
  const update = {
    lane,
    action,
    eagerReducer: null,
    eagerState: null,
    next: null
  };
  enqueueConcurrentHookUpdate(fiber, queue, update, lane);
  scheduleUpdateOnFiber(fiber, lane);
}

// 在事件系统中:
function batchedUpdates(fn, a) {
  const prevIsBatching = isBatchingUpdates;
  isBatchingUpdates = true;
  try {
    return fn(a);
  } finally {
    isBatchingUpdates = prevIsBatching;
    if (!isBatchingUpdates) {
      flushSyncCallbacks();
    }
  }
}
并发特性与 Transition API

Concurrency 模式允许 React 在渲染过程中"可打断",为用户交互保留快速响应的时间窗口。startTransitionuseTransition 会将更新标记为 TransitionLane,让调度器在用户交互与过渡动画之间做优先级调度。

import { startTransition, useTransition } from 'react';

function SearchBox() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  const [list, setList] = useState([]);

  const handleChange = (event) => {
    const value = event.target.value;
    setQuery(value);              // SyncLane,高优先级,立即更新输入框
    startTransition(() => {       // TransitionLane,可被打断
      const filtered = filterHeavyData(value);
      setList(filtered);
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending ? <Spinner /> : <ResultList data={list} />}
    </div>
  );
}
graph LR
    UserInput["用户输入事件"] --> Immediate["同步更新: setQuery"]
    UserInput --> Transition["startTransition"]
    Transition --> Scheduler
    Scheduler -->|高优先级| CommitSync["立即提交输入框"]
    Scheduler -->|低优先级| RenderTransition["后台渲染过滤列表"]
    RenderTransition --> CommitTransition
从并发渲染到提交

performConcurrentWorkOnRoot 会在时间片允许的情况下反复执行渲染任务;一旦渲染完成或任务超时,就会转入 commitRoot。在切换 fiber 树之前,React 会再次确认当前最高优先级是否仍然有效,必要时重新进入渲染循环。

function performConcurrentWorkOnRoot(root) {
  const originalCallbackNode = root.callbackNode;
  const lanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
  if (lanes === NoLanes) {
    return null;
  }

  const exitStatus = renderRootConcurrent(root, lanes);

  if (exitStatus !== RootInProgress) {
    const finishedWork = root.finishedWork;
    commitRoot(root, finishedWork, lanes);
  }

  if (root.callbackNode === originalCallbackNode) {
    return null;
  }
  return performConcurrentWorkOnRoot.bind(null, root);
}

react-noop-renderer

react-noop-renderer 是 React 的测试渲染器,不执行实际的渲染操作,主要用于测试 React 核心逻辑。

核心用途:

  • 测试 React 协调算法
  • 测试 Fiber 架构
  • 测试调度逻辑
  • 验证 React 行为而不依赖 DOM
// react-noop-renderer 使用示例(测试场景)
import ReactNoop from 'react-noop-renderer';

test('测试组件更新', () => {
  function Counter() {
    const [count, setCount] = useState(0);
    return <div>{count}</div>;
  }
  
  const root = ReactNoop.createRoot();
  root.render(<Counter />);
  
  // 验证渲染结果(不涉及真实 DOM)
  expect(root).toMatchRenderedOutput(<div>0</div>);
  
  // 触发更新
  act(() => {
    // 触发 state 更新
  });
  
  expect(root).toMatchRenderedOutput(<div>1</div>);
});

jsx 编译

JSX 是 JavaScript 的语法扩展,允许在 JavaScript 中编写类似 HTML 的标记。浏览器无法直接执行 JSX,需要通过编译器转换为标准的 JavaScript 代码。

编译器

React 的 JSX 编译主要由 Babel 完成。Babel 通过插件 @babel/plugin-transform-react-jsx 将 JSX 语法转换为 React.createElement() 调用(旧版转换)或 jsx() 函数调用(新版转换)。

编译器的核心任务:

  1. 词法分析:将 JSX 代码分解为 tokens
  2. 语法分析:构建抽象语法树(AST)
  3. 转换:将 JSX 节点转换为函数调用
  4. 代码生成:输出标准 JavaScript 代码
graph LR
    A["JSX 源代码"] --> B["词法分析器<br/>Lexer"]
    B --> C["Token 流"]
    C --> D["语法分析器<br/>Parser"]
    D --> E["AST 抽象语法树"]
    E --> F["转换器<br/>Transformer"]
    F --> G["新的 AST"]
    G --> H["代码生成器<br/>Generator"]
    H --> I["JavaScript 代码"]
    
    style A fill:#61dafb
    style I fill:#51cf66

Babel 插件系统:

// Babel 的 JSX 插件配置
{
  "plugins": [
    [
      "@babel/plugin-transform-react-jsx",
      {
        "runtime": "automatic", // 新版 JSX 转换
        "development": false,
        "importSource": "react"
      }
    ]
  ]
}
编译配置与编译过程

React 17+ 引入了全新的 JSX 转换机制,不再需要在文件中显式导入 React。

两种 JSX 转换方式对比:

JSX 代码
// 原始 JSX 代码
function App() {
  return (
    <div className="container">
      <h1>Hello World</h1>
      <p>This is a paragraph</p>
    </div>
  );
}
Babel 的 JSX 转换
解析 JSX

Babel 首先将 JSX 代码解析为 AST(抽象语法树)结构:

// JSX 元素 <div className="container">Hello</div> 的 AST 表示(简化)
{
  type: 'JSXElement',
  openingElement: {
    type: 'JSXOpeningElement',
    name: { type: 'JSXIdentifier', name: 'div' },
    attributes: [
      {
        type: 'JSXAttribute',
        name: { type: 'JSXIdentifier', name: 'className' },
        value: { type: 'StringLiteral', value: 'container' }
      }
    ]
  },
  children: [
    { type: 'StringLiteral', value: 'Hello' }
  ],
  closingElement: {
    type: 'JSXClosingElement',
    name: { type: 'JSXIdentifier', name: 'div' }
  }
}
转换 JSX 元素

旧版转换(Classic Runtime - React 16 及之前):

需要在文件顶部导入 React,JSX 会被转换为 React.createElement() 调用。

// 需要显式导入 React
import React from 'react';

function App() {
  return React.createElement(
    'div',
    { className: 'container' },
    React.createElement('h1', null, 'Hello World'),
    React.createElement('p', null, 'This is a paragraph')
  );
}

新版转换(Automatic Runtime - React 17+):

不需要导入 React,自动从 react/jsx-runtime 导入 JSX 函数。

// 自动导入(由 Babel 添加,开发者不可见)
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';

function App() {
  return _jsxs('div', {
    className: 'container',
    children: [
      _jsx('h1', { children: 'Hello World' }),
      _jsx('p', { children: 'This is a paragraph' })
    ]
  });
}

转换流程对比:

graph TB
    subgraph "旧版转换 Classic"
        A1["JSX 代码"] --> B1["检查 React 导入"]
        B1 -->|没有导入| C1["报错"]
        B1 -->|已导入| D1["转换为 React.createElement"]
        D1 --> E1["输出代码"]
    end
    
    subgraph "新版转换 Automatic"
        A2["JSX 代码"] --> B2["自动导入 jsx runtime"]
        B2 --> D2["转换为 _jsx/_jsxs"]
        D2 --> E2["输出代码"]
    end
    
    style A1 fill:#61dafb
    style A2 fill:#61dafb
    style E1 fill:#51cf66
    style E2 fill:#51cf66
Babel 配置

完整的 Babel 配置示例:

// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-react',
      {
        runtime: 'automatic', // 使用新版 JSX 转换
        development: process.env.NODE_ENV === 'development',
        importSource: 'react' // 从 react 包导入
      }
    ],
    [
      '@babel/preset-env',
      {
        targets: {
          browsers: ['last 2 versions', 'ie >= 11']
        }
      }
    ]
  ],
  plugins: [
    '@babel/plugin-proposal-class-properties',
    '@babel/plugin-transform-runtime'
  ]
};

开发环境与生产环境的差异:

// 开发环境配置(包含调试信息)
{
  "plugins": [
    [
      "@babel/plugin-transform-react-jsx",
      {
        "runtime": "automatic",
        "development": true, // 开启开发模式
        "importSource": "react"
      }
    ]
  ]
}

// 生产环境配置(优化体积)
{
  "plugins": [
    [
      "@babel/plugin-transform-react-jsx",
      {
        "runtime": "automatic",
        "development": false, // 关闭开发模式
        "importSource": "react"
      }
    ]
  ]
}
示例代码
原始 JSX 文件 App.jsx
// App.jsx - 原始 JSX 文件
function App({ title, count }) {
  const handleClick = () => {
    console.log('Clicked!');
  };

  return (
    <div className="app">
      <h1>{title}</h1>
      <div className="counter">
        <p>Count: {count}</p>
        <button onClick={handleClick}>Increment</button>
      </div>
      {count > 5 && <div className="warning">Count is high!</div>}
      <ul>
        {[1, 2, 3].map(item => (
          <li key={item}>Item {item}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;
使用 Babel CLI 转换 JSX
# 安装 Babel 相关包
npm install --save-dev @babel/core @babel/cli @babel/preset-react

# 使用 Babel CLI 转换单个文件
npx babel App.jsx --out-file App.js --presets @babel/preset-react

# 转换整个目录
npx babel src --out-dir dist --presets @babel/preset-react

# 监听文件变化,自动转换
npx babel src --out-dir dist --watch --presets @babel/preset-react
转换后的文件 App.js
// App.js - 转换后的文件(新版 JSX 转换)
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from 'react/jsx-runtime';

function App({ title, count }) {
  const handleClick = () => {
    console.log('Clicked!');
  };

  return _jsxs('div', {
    className: 'app',
    children: [
      _jsx('h1', { children: title }),
      _jsxs('div', {
        className: 'counter',
        children: [
          _jsxs('p', { children: ['Count: ', count] }),
          _jsx('button', {
            onClick: handleClick,
            children: 'Increment'
          })
        ]
      }),
      count > 5 && _jsx('div', {
        className: 'warning',
        children: 'Count is high!'
      }),
      _jsx('ul', {
        children: [1, 2, 3].map(item => 
          _jsxs('li', {
            children: ['Item ', item]
          }, item)
        )
      })
    ]
  });
}

export default App;

关键转换细节:

// 1. 单个子元素使用 jsx()
<div>Hello</div>
// 转换为:
_jsx('div', { children: 'Hello' })

// 2. 多个子元素使用 jsxs()
<div>
  <span>Hello</span>
  <span>World</span>
</div>
// 转换为:
_jsxs('div', {
  children: [
    _jsx('span', { children: 'Hello' }),
    _jsx('span', { children: 'World' })
  ]
})

// 3. key 属性特殊处理
<li key={item.id}>{item.name}</li>
// 转换为:
_jsx('li', { children: item.name }, item.id) // key 作为第三个参数

// 4. 条件渲染保持不变
{condition && <div>Show</div>}
// 转换为:
condition && _jsx('div', { children: 'Show' })

// 5. 循环渲染保持不变
{items.map(item => <li key={item}>{item}</li>)}
// 转换为:
items.map(item => _jsx('li', { children: item }, item))

React 核心流程

React 的核心流程包括**创建(Mount)更新(Update)**两个阶段,分别对应组件的首次渲染和后续更新。这两个阶段都遵循 Render 阶段和 Commit 阶段的双阶段处理模式。

graph TB
    A["应用启动"] --> B["创建流程 Mount"]
    B --> C{"用户交互/状态变化"}
    C --> D["更新流程 Update"]
    D --> C
    
    subgraph "创建流程"
        B --> B1["创建 Fiber 树"]
        B1 --> B2["Render 阶段"]
        B2 --> B3["Commit 阶段"]
        B3 --> B4["首次渲染完成"]
    end
    
    subgraph "更新流程"
        D --> D1["触发更新"]
        D1 --> D2["调度更新"]
        D2 --> D3["Render 阶段 Diff"]
        D3 --> D4["Commit 阶段"]
        D4 --> D5["更新完成"]
    end
    
    style B fill:#61dafb
    style D fill:#ffd700
创建

组件的创建(Mount)流程是指组件首次渲染到页面的整个过程,包括创建 Fiber 树、执行渲染逻辑、生成 DOM 并挂载到页面。

创建流程的核心步骤:

  1. 创建 FiberRoot 和 RootFiber
  2. Render 阶段:构建 Fiber 树
  3. Commit 阶段:将 Fiber 树渲染到 DOM

完整的创建流程:

sequenceDiagram
    participant App as React 应用
    participant Root as FiberRoot
    participant Render as Render 阶段
    participant Commit as Commit 阶段
    participant DOM as 真实 DOM
    
    App->>Root: createRoot(container)
    Root->>Root: 创建 FiberRoot
    Root->>Root: 创建 RootFiber
    
    App->>Root: root.render(<App />)
    Root->>Render: 进入 Render 阶段
    
    Render->>Render: beginWork - 处理每个 Fiber
    Render->>Render: 创建子 Fiber 节点
    Render->>Render: 深度优先遍历
    Render->>Render: completeWork - 完成 Fiber
    Render->>Render: 构建完整 Fiber 树
    
    Render->>Commit: 进入 Commit 阶段
    Commit->>Commit: before mutation - 执行 getSnapshotBeforeUpdate
    Commit->>DOM: mutation - 创建 DOM 节点并插入
    Commit->>Commit: layout - 执行 useLayoutEffect/componentDidMount
    Commit->>Commit: 异步执行 useEffect
    
    Commit->>App: 渲染完成

代码示例 - 创建流程:

// 1. 创建 React 应用(React 18)
import { createRoot } from 'react-dom/client';

function App() {
  return <div>Hello React</div>;
}

// 创建根节点
const container = document.getElementById('root');
const root = createRoot(container);

// 触发首次渲染
root.render(<App />);

// 2. 内部创建流程(简化)
function createRoot(container) {
  // 创建 FiberRoot(整个应用的根)
  const fiberRoot = {
    containerInfo: container,  // DOM 容器
    current: null,             // 指向 RootFiber
    finishedWork: null,        // 已完成的工作
  };
  
  // 创建 RootFiber(Fiber 树的根)
  const rootFiber = createFiber(HostRoot, null, null);
  
  // 相互引用
  fiberRoot.current = rootFiber;
  rootFiber.stateNode = fiberRoot;
  
  return {
    render(element) {
      // 创建 Update 对象
      const update = {
        payload: { element }, // 要渲染的元素
      };
      
      // 将 Update 加入更新队列
      enqueueUpdate(rootFiber, update);
      
      // 调度渲染
      scheduleUpdateOnFiber(rootFiber);
    }
  };
}

// 3. Render 阶段 - 构建 Fiber 树
function renderRootSync(root) {
  let workInProgress = root.current.alternate;
  
  if (workInProgress === null) {
    // 首次渲染,创建 workInProgress 树
    workInProgress = createWorkInProgress(root.current);
  }
  
  // 工作循环
  workLoopSync(workInProgress);
  
  // 标记完成的工作
  root.finishedWork = workInProgress;
}

function workLoopSync(fiber) {
  while (fiber !== null) {
    fiber = performUnitOfWork(fiber);
  }
}

function performUnitOfWork(fiber) {
  // beginWork:处理当前 Fiber,创建子 Fiber
  const next = beginWork(fiber);
  
  if (next !== null) {
    return next; // 返回子 Fiber
  }
  
  // 没有子节点,执行 completeWork
  completeUnitOfWork(fiber);
  
  // 返回下一个要处理的 Fiber(兄弟或父节点)
  return getNextUnitOfWork(fiber);
}

// 4. Commit 阶段 - 提交到 DOM
function commitRoot(root) {
  const finishedWork = root.finishedWork;
  
  // 阶段 1: before mutation
  commitBeforeMutationEffects(finishedWork);
  
  // 阶段 2: mutation - 操作 DOM
  commitMutationEffects(finishedWork);
  
  // 切换 current 指针
  root.current = finishedWork;
  
  // 阶段 3: layout
  commitLayoutEffects(finishedWork);
  
  // 异步执行 effects
  schedulePassiveEffects(finishedWork);
}

Fiber 树构建示例:

// JSX 结构
<App>
  <Header>
    <h1>Title</h1>
  </Header>
  <Content>
    <p>Text</p>
  </Content>
</App>

// 构建的 Fiber 树结构
const fiberTree = {
  tag: FunctionComponent,
  type: App,
  child: {                    // App 的子节点
    tag: FunctionComponent,
    type: Header,
    sibling: {                // Header 的兄弟节点
      tag: FunctionComponent,
      type: Content,
      child: {
        tag: HostComponent,
        type: 'p',
        child: {
          tag: HostText,
          text: 'Text'
        }
      }
    },
    child: {                  // Header 的子节点
      tag: HostComponent,
      type: 'h1',
      child: {
        tag: HostText,
        text: 'Title'
      }
    }
  }
};
更新

组件的更新(Update)流程是指组件状态或属性变化后重新渲染的过程。与创建流程类似,更新流程也经历 Render 和 Commit 两个阶段,但会执行 Diff 算法来复用已有节点。

触发更新的方式:

// 1. useState/useReducer 触发更新
function Counter() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(count + 1); // 触发更新
  };
  
  return <button onClick={handleClick}>{count}</button>;
}

// 2. 类组件 setState 触发更新
class Counter extends React.Component {
  state = { count: 0 };
  
  handleClick = () => {
    this.setState({ count: this.state.count + 1 }); // 触发更新
  };
  
  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>;
  }
}

// 3. forceUpdate 强制更新
class MyComponent extends React.Component {
  handleClick = () => {
    this.forceUpdate(); // 跳过 shouldComponentUpdate
  };
}

// 4. 根节点重新渲染
root.render(<App newProp="value" />); // 触发更新

更新流程:

graph TB
    A["触发更新"] --> B["创建 Update 对象"]
    B --> C["加入 UpdateQueue"]
    C --> D["调度更新 scheduleUpdateOnFiber"]
    D --> E{"判断优先级"}
    E -->|高优先级| F["同步更新"]
    E -->|普通优先级| G["异步调度"]
    F --> H["Render 阶段"]
    G --> H
    H --> I["beginWork - Diff 算法"]
    I --> J["复用或创建 Fiber"]
    J --> K["completeWork"]
    K --> L["标记副作用"]
    L --> M["Commit 阶段"]
    M --> N["执行 DOM 操作"]
    N --> O["执行生命周期/Hooks"]
    O --> P["更新完成"]
    
    style A fill:#ffd700
    style H fill:#ff6b6b
    style M fill:#51cf66

更新流程核心代码:

// 1. 触发更新 - useState 的实现
function useState(initialState) {
  const hook = {
    memoizedState: initialState,
    queue: { pending: null }
  };
  
  const dispatch = (action) => {
    // 创建 Update
    const update = {
      action,
      next: null
    };
    
    // 加入更新队列
    enqueueUpdate(hook.queue, update);
    
    // 调度更新
    scheduleUpdateOnFiber(currentFiber);
  };
  
  return [hook.memoizedState, dispatch];
}

// 2. Render 阶段 - beginWork 执行 Diff
function beginWork(current, workInProgress) {
  // current: 旧 Fiber
  // workInProgress: 新 Fiber
  
  if (current !== null) {
    // 更新流程
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    
    // 比较 props 是否变化
    if (oldProps === newProps && !hasContextChanged()) {
      // props 没变化,可以复用
      return bailoutOnAlreadyFinishedWork(current, workInProgress);
    }
  }
  
  // 根据不同的 tag 处理
  switch (workInProgress.tag) {
    case FunctionComponent:
      return updateFunctionComponent(current, workInProgress);
    case ClassComponent:
      return updateClassComponent(current, workInProgress);
    case HostComponent:
      return updateHostComponent(current, workInProgress);
    default:
      return null;
  }
}

// 3. Diff 算法 - 协调子节点
function reconcileChildren(current, workInProgress, nextChildren) {
  if (current === null) {
    // 创建流程
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
  } else {
    // 更新流程 - 执行 Diff
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren
    );
  }
}

// Diff 算法的核心逻辑
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
  // 处理单个元素
  if (typeof newChild === 'object' && newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE:
        return placeSingleChild(
          reconcileSingleElement(returnFiber, currentFirstChild, newChild)
        );
    }
  }
  
  // 处理数组(多个子元素)
  if (Array.isArray(newChild)) {
    return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
  }
  
  // 处理文本节点
  if (typeof newChild === 'string' || typeof newChild === 'number') {
    return placeSingleChild(
      reconcileSingleTextNode(returnFiber, currentFirstChild, newChild)
    );
  }
  
  // 删除旧节点
  return deleteRemainingChildren(returnFiber, currentFirstChild);
}

// 4. Commit 阶段 - 执行副作用
function commitMutationEffects(finishedWork) {
  // 遍历 Fiber 树,执行副作用
  commitMutationEffectsOnFiber(finishedWork);
}

function commitMutationEffectsOnFiber(fiber) {
  const flags = fiber.flags;
  
  // 处理 ref
  if (flags & Ref) {
    const current = fiber.alternate;
    if (current !== null) {
      commitDetachRef(current);
    }
  }
  
  // 处理副作用
  const primaryFlags = flags & (Placement | Update | Deletion);
  
  switch (primaryFlags) {
    case Placement: {
      // 插入节点
      commitPlacement(fiber);
      fiber.flags &= ~Placement;
      break;
    }
    case Update: {
      // 更新节点
      const current = fiber.alternate;
      commitWork(current, fiber);
      break;
    }
    case Deletion: {
      // 删除节点
      commitDeletion(fiber);
      break;
    }
  }
  
  // 递归处理子节点
  if (fiber.child !== null) {
    commitMutationEffectsOnFiber(fiber.child);
  }
  
  // 处理兄弟节点
  if (fiber.sibling !== null) {
    commitMutationEffectsOnFiber(fiber.sibling);
  }
}

更新优化策略:

// 1. React.memo - 浅比较 props
const MemoizedComponent = React.memo(function MyComponent(props) {
  return <div>{props.value}</div>;
});

// 2. useMemo - 缓存计算结果
function ExpensiveComponent({ data }) {
  const result = useMemo(() => {
    return expensiveCalculation(data);
  }, [data]);
  
  return <div>{result}</div>;
}

// 3. useCallback - 缓存函数引用
function ParentComponent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []); // 依赖为空,函数不会重新创建
  
  return <ChildComponent onClick={handleClick} />;
}

// 4. PureComponent - 自动浅比较
class MyComponent extends React.PureComponent {
  render() {
    return <div>{this.props.value}</div>;
  }
}

// 5. shouldComponentUpdate - 自定义比较
class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.value !== this.props.value;
  }
  
  render() {
    return <div>{this.props.value}</div>;
  }
}

小结:并发更新、自动批处理、错误边界与 Suspense 的协同,让 React 能够在保持 UI 响应性的前提下构建复杂的可恢复交互流程。

Hooks 内部机制

Hooks 让函数组件具备状态与副作用能力,其背后依赖于 Fiber 上的 Hook 链表、dispatcher 的动态切换以及副作用队列的统一调度。

Hook 链表与 Fiber 绑定

每个函数组件在 Render 阶段都会创建一条 Hook 链表,保存在当前 Fiber 的 memoizedState 属性上。链表遍历顺序与 Hooks 调用顺序严格一致,这也是"Hooks 不能放在条件语句内"的根本原因。

function renderWithHooks(current, workInProgress, Component, props) {
  renderLanes = workInProgress.lanes;
  currentlyRenderingFiber = workInProgress;
  workInProgressHook = current !== null ? current.memoizedState : null;
  
  ReactCurrentDispatcher.current = current === null
    ? HooksDispatcherOnMount
    : HooksDispatcherOnUpdate;

  let children = Component(props);
  
  finishRenderingHooks();
  return children;
}
graph LR
    A["Function Component"] --> B["renderWithHooks"]
    B --> C["HooksDispatcherOnMount"]
    C --> D["mountState"]
    D --> E["Hook 节点"]
    E --> F["next Hook"]

Hook 节点结构(简化):

const hook = {
  memoizedState: initialState, // 保存当前值
  baseState: initialState,
  baseQueue: null,
  queue: {
    pending: null,            // 循环链表,存放更新
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: initialState,
  },
  next: null,                 // 指向下一个 Hook
};

dispatcher 的挂载与切换

  • 初次渲染:使用 HooksDispatcherOnMount,每个 Hook 会调用 mountXxx 分支,创建 Hook 节点并挂载更新队列。
  • 更新阶段:切换为 HooksDispatcherOnUpdate,调用 updateXxx 分支,复用旧的 Hook 链表节点,并按顺序取出保存的状态。
  • 非严格模式下的双渲染:React 在开发环境会多次执行函数组件,以检验 Hooks 的副作用是否幂等。
const HooksDispatcherOnMount = {
  useState: mountState,
  useReducer: mountReducer,
  useEffect: mountEffect,
  useLayoutEffect: mountLayoutEffect,
  // ...
};

const HooksDispatcherOnUpdate = {
  useState: updateState,
  useReducer: updateReducer,
  useEffect: updateEffect,
  useLayoutEffect: updateLayoutEffect,
};

useState 与 useReducer 的调度链

useStateuseReducer 共用同一套更新机制:

  1. 在 Hook queue 上创建循环链表节点。
  2. 调度 scheduleUpdateOnFiber
  3. 在 Render 阶段合并所有 update(processUpdateQueue)。
function mountState(initialState) {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    hook.memoizedState = initialState();
  } else {
    hook.memoizedState = initialState;
  }
  const queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: hook.memoizedState,
  };
  hook.queue = queue;
  const dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
  return [hook.memoizedState, dispatch];
}

useEffect 与 useLayoutEffect 的执行时序

两类副作用都会在 Render 阶段收集依赖,但执行时机不同:

  • useLayoutEffect:在 Commit 的 layout 阶段同步执行,适合操作 DOM、测量布局。
  • useEffect:在 Commit 结束后异步执行,适合订阅、网络请求等不会阻塞渲染的副作用。
sequenceDiagram
    participant Render as Render 阶段
    participant Layout as layout 阶段
    participant Passive as passive 阶段

    Render->>Render: 收集 useEffect/useLayoutEffect
    Render->>Layout: 注册 layoutEffects
    Render->>Passive: 注册 passiveEffects
    Layout->>Layout: 执行 useLayoutEffect / cleanup
    Passive->>Passive: 执行 useEffect / cleanup
function commitLayoutEffects(root, finishedWork) {
  // layoutEffectsList 是一个单向链表
  while (nextEffect !== null) {
    commitLayoutEffectOnFiber(finishedWork);
    nextEffect = nextEffect.nextEffect;
  }
}

function flushPassiveEffects() {
  // passiveEffectsList 同样使用链表存储
  while (pendingPassiveHookEffects.length > 0) {
    const effect = pendingPassiveHookEffects.pop();
    invokeGuardedCallback(effect.destroy);
    invokeGuardedCallback(effect.create);
  }
}

useTransition 与 useDeferredValue 的内部协作

这两个并发特性分别通过:

  • useTransition:返回 [isPending, startTransition],底层创建新的 TransitionLane,并在 Hook queue 中记录 pendingLanes
  • useDeferredValue:在 Hook 内部调用 useStatestartTransition,将低优先级更新延后执行。
function updateTransition() {
  const hook = updateWorkInProgressHook();
  const pending = hook.memoizedState;
  const queue = hook.queue;
  // 如果有 Transition 尚未完成,则保持 isPending=true
  const currentTransition = queue.pending;
  hook.memoizedState = currentTransition;
  return [pending !== null, queue.startTransition];
}

这些机制保证了函数组件在保持纯粹的同时,仍能表达状态管理、异步副作用与并发控制等丰富能力。

事件系统深入

React 在 react-dom 中实现了一套跨浏览器的合成事件系统,核心目标是统一事件模型、减少监听器数量并支持优先级调度。

合成事件分层架构

graph TD
    Native["原生事件"] --> Listener["事件监听包装"]
    Listener --> Plugin["事件插件系统"]
    Plugin --> SyntheticEvent["合成事件对象"]
    SyntheticEvent --> Dispatch["分发阶段"]
    Dispatch --> UpdateQueue["更新任务调度"]
  • 监听阶段:在根容器(rootContainerInstance)上注册捕获与冒泡监听器,统一由事件系统处理。
  • 插件阶段:通过 DOMEventPropertiesSimpleEventPlugin 等插件将原生事件映射为合成事件。
  • 合成事件对象SyntheticEvent 提供跨平台的接口,内部使用对象池减少 GC。
  • 分发与调度:按照捕获→冒泡顺序执行监听函数,并为离散事件(如点击)和连续事件(如滚动)赋予不同 Scheduler 优先级。
function dispatchEventForPluginEventSystem(
  domEventName,
  eventSystemFlags,
  nativeEvent,
  targetInst,
  rootContainerElement
) {
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue = [];
  // 组装 dispatchQueue:捕获/冒泡阶段的 listener 与事件对象
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags
  );
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

事件优先级与 Scheduler

React 会根据事件类型分配优先级,从而决定使用 DiscreteEventPriorityContinuousEventPriority 等不同 lane:

const DiscreteEventPriority = SyncLane;
const ContinuousEventPriority = InputContinuousLane;
const DefaultEventPriority = DefaultLane;

const eventPriorityMap = {
  click: DiscreteEventPriority,
  keydown: DiscreteEventPriority,
  mousemove: ContinuousEventPriority,
  scroll: ContinuousEventPriority,
};

function getEventPriority(domEventName) {
  return eventPriorityMap[domEventName] ?? DefaultEventPriority;
}

当事件触发时,React 会调用 requestEventTimerequestUpdateLane 为更新匹配相同优先级的 lane,使得高优先级交互能够在并发模式下优先处理。

Pointer 捕获与冒泡策略

React 针对 Pointer 事件提供了统一的捕获/冒泡处理:

  • 通过 accumulateSinglePhaseListeners 收集捕获链。
  • dispatchQueue 中记录 listener 与阶段信息。
  • 对支持 capture 的事件(如 pointerenter)在根节点执行模拟捕获。
sequenceDiagram
    participant Root
    participant Target
    participant Scheduler

    Root->>Root: 注册顶层监听
    Target->>Root: 原生事件冒泡
    Root->>Scheduler: 根据事件优先级调度
    Scheduler->>Target: 执行捕获 listener
    Scheduler->>Target: 执行冒泡 listener

事件系统与批处理

React 事件处理函数默认被 batchedUpdates 包裹,多个 setState 会在一次 Render 中合并,减少 DOM 重绘次数;只有当开发者显式调用 flushSync 时才会即时刷新。

服务端渲染与 Server Components

React 18 提供了 Streaming SSR 与 Server Components(RSC),让服务端与客户端共享同一套组件模型。

Streaming SSR 渲染流水线

flowchart TD
    Start["renderToPipeableStream/ renderToReadableStream"]
    Start --> Shell["生成 shell HTML"]
    Shell --> Pipe["pipe() / ReadableStream pipe"]
    Pipe --> Client["客户端接收并执行 hydrateRoot"]
    Start --> Chunks["异步数据块 Suspense 边界"]
    Chunks --> Pipe

核心 API:

import { renderToPipeableStream } from 'react-dom/server';

const stream = renderToPipeableStream(<App />, {
  onShellReady() {
    stream.pipe(res);
  },
  onAllReady() {
    res.end();
  }
});
  • onShellReady:当 shell 内容准备好即可推送到客户端,实现"先展示骨架"体验。
  • Suspense data chunk:遇到 Suspense fallback 时,服务端会输出占位 HTML,待数据准备好后再推送替换块。
  • hydrateRoot:客户端接手后与现有 DOM 对比,复用而非重建节点。

Server Components 基础流程

RSC 将组件拆分为 Server/Client 两类,通过 react-server-dom-webpack 管理传输协议。

sequenceDiagram
    participant Client
    participant Server
    participant Flight

    Client->>Server: 请求 RSC payload
    Server->>Flight: 渲染 Server Component
    Flight-->>Client: 发送 JSON payload
    Client->>Client: 解码为 React Element Tree
    Client->>Client: client components hydrate

关键点:

  • Server Component 只能使用服务端资源(DB、文件系统),不支持浏览器 API。
  • 数据通过 Flight 协议序列化为 JSON 流,Client 根据 module_reference 懒加载对应 Client Component。
  • use API 允许在 Server Component 中直接 await Promise,使数据依赖与视图结构同步。
// Server Component
export default async function Page() {
  const user = await fetchUser();
  return (
    <Layout>
      <Sidebar user={user} />
      <Content />
    </Layout>
  );
}

Hydration 与局部更新

  • hydrateRootrenderRootConcurrent 共用 Fiber 双缓存逻辑。
  • 当 Streaming chunk 抵达时,Client 会调用 scheduleTask 拉起对应 Suspense fiber 的重渲染。

构建体系与 Feature Flag

React 官方仓库使用 Rollup 构建多个目标产物,并通过 Feature Flag 精细控制功能开关。

Rollup 构建管线

graph LR
    Entry["packages/react/index.js"] --> Rollup["Rollup 配置"]
    Rollup --> Builds["不同 bundle"]
    Builds -->|UMD| ReactUMD["react.development.js"]
    Builds -->|CJS| ReactCJS["index.js"]
    Builds -->|ESM| ReactESM["react.development.mjs"]

构建脚本位于 scripts/rollup/build.js

const bundles = [
  {
    entry: 'packages/react/index.js',
    global: 'React',
    externals: ['react'],
    bundleType: UMD_DEV,
  },
  // ...
];

function createBundle(bundle, bundleType, filename, globalName) {
  return {
    input: { entry: bundle.entry },
    output: { file: filename, format: getFormat(bundleType), name: globalName },
    plugins: getPlugins(bundleType),
  };
}
  • bundleType 控制输出格式(UMD/CJS/ESM)、开发/生产模式。
  • if (__DEV__) 等环境变量通过 Rollup 插件在构建时替换,提高 dead code elimination 效果。

Feature Flag 与入口选择

React 通过 shared/ReactFeatureFlags.* 定义不同环境下的特性组合:

export const enableProfilerTimer = __PROFILE__;
export const enableNewReconciler = false;

构建时会根据目标(wwwfb-wwwopen-source)选择不同的 flag 文件,从而在不发布多份源码的情况下定制差异化行为。

开发体验:调试与 Profiling

  • 使用 yarn build react/index,react-dom/index --type=NODE_DEV 构建本地调试包。
  • --type=FB_DEV 会启用 Facebook 内部专用 flag,方便对比差异。
  • Profiling 构建(--profile)会启用 enableProfilerTimer,用于分析 Scheduler 与渲染性能。

测试与调试工具链

React 维护多套渲染器与测试框架,确保核心算法在不同宿主环境下表现一致。

React Noop Renderer

react-noop-renderer 提供"无宿主"的渲染实现,常用于 Fiber 行为测试。

import ReactNoop from 'react-noop-renderer';
import { act } from 'react-dom/test-utils';

test('state updates', () => {
  function Counter() {
    const [count, setCount] = useState(0);
    return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
  }

  const root = ReactNoop.createRoot();
  act(() => {
    root.render(<Counter />);
  });

  expect(root).toMatchRenderedOutput(<button>0</button>);
});

react-test-renderer 与快照

  • react-test-renderer 允许在 Node 环境渲染组件树并与快照对比。
  • 支持 act API,确保所有副作用在断言前执行。
import renderer, { act } from 'react-test-renderer';

it('renders correctly', () => {
  let tree;
  act(() => {
    tree = renderer.create(<App />);
  });
  expect(tree.toJSON()).toMatchSnapshot();
});

DevTools 与 Fiber 调试

DevTools 通过 react-reconciler__REACT_DEVTOOLS_GLOBAL_HOOK__ 注入,实现:

  • Fiber 树可视化、props/state 浏览。
  • Profiler Flamechart,基于调度时间段生成热点图。
  • 记录网络边界(Suspense)、State 变更历史。
graph LR
    ReactApp --> Hook["__REACT_DEVTOOLS_GLOBAL_HOOK__"]
    Hook --> Bridge["DevTools Bridge"]
    Bridge --> Extension["Chrome/Standalone DevTools"]

End-to-End 测试

React 使用 jest + karma + 浏览器驱动执行 E2E 测试,确保在真实 DOM 环境下行为一致。

  • fixtures/ 目录包含多种宿主环境示例。
  • packages/react-dom/src/events/__tests__ 覆盖事件系统的冒泡、捕获、阻止默认等场景。

数据结构与算法

React 源码中大量运用了各种数据结构和算法来优化性能和实现复杂功能。理解这些数据结构和算法是深入理解 React 源码的关键。

数据结构

特性

堆是一种特殊的完全二叉树结构,分为最小堆最大堆。React 的 Scheduler 使用最小堆来管理任务队列。

最小堆的核心特性:

  1. 父节点 ≤ 子节点:父节点的值总是小于或等于子节点
  2. 完全二叉树:除最后一层外,其他层都是满的
  3. 数组存储:可以用数组高效存储,无需指针
  4. 时间复杂度
    • 插入:O(log n)
    • 删除最小值:O(log n)
    • 查找最小值:O(1)

数组存储规则:

// 对于索引为 i 的节点
const parentIndex = Math.floor((i - 1) / 2);  // 父节点索引
const leftChildIndex = 2 * i + 1;             // 左子节点索引
const rightChildIndex = 2 * i + 2;            // 右子节点索引

最小堆实现:

class MinHeap {
  constructor() {
    this.heap = [];
  }
  
  // 获取堆顶元素(最小值)
  peek() {
    return this.heap[0];
  }
  
  // 获取堆大小
  size() {
    return this.heap.length;
  }
  
  // 插入元素
  push(node) {
    // 1. 将元素添加到数组末尾
    this.heap.push(node);
    
    // 2. 上浮操作,维护堆的性质
    this.siftUp(this.heap.length - 1);
  }
  
  // 删除并返回堆顶元素
  pop() {
    if (this.heap.length === 0) {
      return null;
    }
    
    const first = this.heap[0];
    const last = this.heap.pop();
    
    if (this.heap.length > 0) {
      // 将最后一个元素放到堆顶
      this.heap[0] = last;
      // 下沉操作,维护堆的性质
      this.siftDown(0);
    }
    
    return first;
  }
  
  // 上浮操作
  siftUp(index) {
    const node = this.heap[index];
    
    while (index > 0) {
      const parentIndex = Math.floor((index - 1) / 2);
      const parent = this.heap[parentIndex];
      
      // 如果当前节点 >= 父节点,停止上浮
      if (node.expirationTime >= parent.expirationTime) {
        break;
      }
      
      // 交换当前节点和父节点
      this.heap[index] = parent;
      index = parentIndex;
    }
    
    this.heap[index] = node;
  }
  
  // 下沉操作
  siftDown(index) {
    const node = this.heap[index];
    const length = this.heap.length;
    const halfLength = length >>> 1; // 只需要遍历到有子节点的位置
    
    while (index < halfLength) {
      const leftIndex = 2 * index + 1;
      const rightIndex = leftIndex + 1;
      const left = this.heap[leftIndex];
      const right = this.heap[rightIndex];
      
      // 找出子节点中较小的一个
      let minIndex = leftIndex;
      let minChild = left;
      
      if (rightIndex < length && right.expirationTime < left.expirationTime) {
        minIndex = rightIndex;
        minChild = right;
      }
      
      // 如果当前节点 <= 较小的子节点,停止下沉
      if (node.expirationTime <= minChild.expirationTime) {
        break;
      }
      
      // 交换当前节点和较小的子节点
      this.heap[index] = minChild;
      index = minIndex;
    }
    
    this.heap[index] = node;
  }
}

堆的操作流程:

graph TB
    subgraph "插入操作"
        A1["添加到数组末尾"] --> A2["与父节点比较"]
        A2 -->|小于父节点| A3["交换位置"]
        A3 --> A2
        A2 -->|大于等于父节点| A4["插入完成"]
    end
    
    subgraph "删除操作"
        B1["保存堆顶元素"] --> B2["将最后元素移到堆顶"]
        B2 --> B3["与子节点比较"]
        B3 -->|大于较小子节点| B4["交换位置"]
        B4 --> B3
        B3 -->|小于等于子节点| B5["删除完成"]
    end
    
    style A4 fill:#51cf66
    style B5 fill:#51cf66
React 中的应用

React Scheduler 使用最小堆管理任务队列,确保始终优先执行过期时间最早(优先级最高)的任务。

// Scheduler 中的任务队列实现
const taskQueue = []; // 最小堆存储待执行任务
const timerQueue = []; // 最小堆存储延迟任务

// 调度任务
function unstable_scheduleCallback(priorityLevel, callback, options) {
  const currentTime = getCurrentTime();
  
  let startTime;
  if (typeof options === 'object' && options !== null) {
    const delay = options.delay;
    startTime = typeof delay === 'number' 
      ? currentTime + delay 
      : currentTime;
  } else {
    startTime = currentTime;
  }
  
  // 根据优先级计算过期时间
  let timeout;
  switch (priorityLevel) {
    case ImmediatePriority:
      timeout = IMMEDIATE_PRIORITY_TIMEOUT; // -1,立即过期
      break;
    case UserBlockingPriority:
      timeout = USER_BLOCKING_PRIORITY_TIMEOUT; // 250ms
      break;
    case NormalPriority:
      timeout = NORMAL_PRIORITY_TIMEOUT; // 5000ms
      break;
    case LowPriority:
      timeout = LOW_PRIORITY_TIMEOUT; // 10000ms
      break;
    case IdlePriority:
      timeout = IDLE_PRIORITY_TIMEOUT; // 最大整数
      break;
    default:
      timeout = NORMAL_PRIORITY_TIMEOUT;
  }
  
  const expirationTime = startTime + timeout;
  
  const newTask = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    sortIndex: -1,
  };
  
  if (startTime > currentTime) {
    // 延迟任务,加入 timerQueue
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);
    
    if (peek(taskQueue) === null && peek(timerQueue) === newTask) {
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    // 立即执行的任务,加入 taskQueue
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask); // 使用堆的 push 操作
    
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }
  }
  
  return newTask;
}

// 执行任务队列
function flushWork(hasTimeRemaining, initialTime) {
  isHostCallbackScheduled = false;
  isPerformingWork = true;
  
  try {
    return workLoop(hasTimeRemaining, initialTime);
  } finally {
    currentTask = null;
    isPerformingWork = false;
  }
}

function workLoop(hasTimeRemaining, initialTime) {
  let currentTime = initialTime;
  currentTask = peek(taskQueue); // 获取堆顶任务(优先级最高)
  
  while (currentTask !== null) {
    if (
      currentTask.expirationTime > currentTime &&
      (!hasTimeRemaining || shouldYieldToHost())
    ) {
      // 任务未过期且需要让出控制权
      break;
    }
    
    const callback = currentTask.callback;
    if (typeof callback === 'function') {
      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); // 使用堆的 pop 操作
        }
      }
    } else {
      pop(taskQueue);
    }
    
    currentTask = peek(taskQueue);
  }
  
  return currentTask !== null;
}

优先级队列示意图:

graph TD
    A["任务队列<br/>最小堆"] --> B["任务1: 过期时间 100ms<br/>UserBlockingPriority"]
    A --> C["任务2: 过期时间 5000ms<br/>NormalPriority"]
    A --> D["任务3: 过期时间 10000ms<br/>LowPriority"]
    B --> E["任务4: 过期时间 250ms"]
    B --> F["任务5: 过期时间 300ms"]
    
    style B fill:#ff6b6b
    style C fill:#ffd700
    style D fill:#51cf66
链表
基本使用

链表是一种线性数据结构,每个节点包含数据和指向下一个节点的指针。React 使用链表来组织 Fiber 节点和更新队列。

链表的基本结构:

// 单向链表节点
class ListNode {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

// 双向链表节点
class DoublyListNode {
  constructor(value) {
    this.value = value;
    this.prev = null;
    this.next = null;
  }
}

// 循环链表
class CircularListNode {
  constructor(value) {
    this.value = value;
    this.next = this; // 指向自己,形成环
  }
}

链表的基本操作:

class LinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
    this.size = 0;
  }
  
  // 在头部插入
  prepend(value) {
    const newNode = new ListNode(value);
    
    if (this.head === null) {
      this.head = newNode;
      this.tail = newNode;
    } else {
      newNode.next = this.head;
      this.head = newNode;
    }
    
    this.size++;
  }
  
  // 在尾部插入
  append(value) {
    const newNode = new ListNode(value);
    
    if (this.tail === null) {
      this.head = newNode;
      this.tail = newNode;
    } else {
      this.tail.next = newNode;
      this.tail = newNode;
    }
    
    this.size++;
  }
  
  // 删除节点
  remove(value) {
    if (this.head === null) {
      return null;
    }
    
    // 如果要删除的是头节点
    if (this.head.value === value) {
      const removed = this.head;
      this.head = this.head.next;
      if (this.head === null) {
        this.tail = null;
      }
      this.size--;
      return removed;
    }
    
    // 查找要删除的节点
    let current = this.head;
    while (current.next !== null) {
      if (current.next.value === value) {
        const removed = current.next;
        current.next = current.next.next;
        if (removed === this.tail) {
          this.tail = current;
        }
        this.size--;
        return removed;
      }
      current = current.next;
    }
    
    return null;
  }
  
  // 遍历链表
  traverse(callback) {
    let current = this.head;
    while (current !== null) {
      callback(current.value);
      current = current.next;
    }
  }
}

// 使用示例
const list = new LinkedList();
list.append(1);
list.append(2);
list.append(3);
list.prepend(0);

list.traverse(value => console.log(value)); // 0, 1, 2, 3
React 中的应用
fiber

Fiber 架构使用链表结构来组织 Fiber 树,每个 Fiber 节点通过三个指针连接:child(第一个子节点)、sibling(兄弟节点)、return(父节点)。

// Fiber 节点的链表结构
const fiber = {
  // 链表指针
  return: null,   // 指向父 Fiber(链表的前驱)
  child: null,    // 指向第一个子 Fiber(子链表的头)
  sibling: null,  // 指向下一个兄弟 Fiber(链表的后继)
  
  // 节点数据
  tag: WorkTag,
  type: any,
  stateNode: any,
  
  // 其他属性...
};

Fiber 树的链表结构示例:

// JSX 结构
<div>
  <h1>Title</h1>
  <p>Content</p>
  <span>Footer</span>
</div>

// Fiber 链表结构
const divFiber = {
  type: 'div',
  child: h1Fiber,      // 指向第一个子节点
  sibling: null,       // 没有兄弟节点
  return: parentFiber  // 指向父节点
};

const h1Fiber = {
  type: 'h1',
  child: textFiber1,
  sibling: pFiber,     // 指向兄弟节点 p
  return: divFiber     // 指向父节点 div
};

const pFiber = {
  type: 'p',
  child: textFiber2,
  sibling: spanFiber,  // 指向兄弟节点 span
  return: divFiber
};

const spanFiber = {
  type: 'span',
  child: textFiber3,
  sibling: null,       // 最后一个兄弟
  return: divFiber
};

Fiber 树的遍历(深度优先):

// 遍历 Fiber 树
function traverseFiberTree(fiber) {
  let current = fiber;
  
  while (current !== null) {
    // 处理当前节点
    console.log(current.type);
    
    // 1. 优先遍历子节点
    if (current.child !== null) {
      current = current.child;
      continue;
    }
    
    // 2. 没有子节点,遍历兄弟节点
    if (current.sibling !== null) {
      current = current.sibling;
      continue;
    }
    
    // 3. 回到父节点,找父节点的兄弟节点
    while (current.return !== null) {
      current = current.return;
      
      if (current.sibling !== null) {
        current = current.sibling;
        break;
      }
    }
    
    // 如果回到根节点且没有兄弟,遍历结束
    if (current.return === null && current.sibling === null) {
      break;
    }
  }
}

Fiber 链表遍历流程:

graph TD
    A["div"] --> B["h1"]
    B --> C["text: Title"]
    B --> D["p"]
    D --> E["text: Content"]
    D --> F["span"]
    F --> G["text: Footer"]
    
    A -.child.-> B
    B -.child.-> C
    B -.sibling.-> D
    D -.child.-> E
    D -.sibling.-> F
    F -.child.-> G
    
    style A fill:#61dafb
    style B fill:#ffd700
    style D fill:#ffd700
    style F fill:#ffd700
UpdateQueue

UpdateQueue 使用循环链表来存储更新对象,确保更新按顺序执行。

// Update 对象(链表节点)
const update = {
  eventTime: number,         // 更新触发时间
  lane: Lane,               // 更新优先级
  tag: UpdateTag,           // 更新类型
  payload: any,             // 更新的数据
  callback: Function | null, // 更新完成后的回调
  next: null,               // 指向下一个 Update(循环链表)
};

// UpdateQueue 结构
const updateQueue = {
  baseState: any,           // 基础状态
  firstBaseUpdate: null,    // 第一个未处理的 Update
  lastBaseUpdate: null,     // 最后一个未处理的 Update
  shared: {
    pending: null,          // 新的 Update 循环链表
  },
  effects: null,            // 有 callback 的 Update 列表
};

// 创建 UpdateQueue
function initializeUpdateQueue(fiber) {
  const queue = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null,
    },
    effects: null,
  };
  fiber.updateQueue = queue;
}

// 将 Update 加入队列(循环链表)
function enqueueUpdate(fiber, update) {
  const updateQueue = fiber.updateQueue;
  if (updateQueue === null) {
    return;
  }
  
  const sharedQueue = updateQueue.shared;
  const pending = sharedQueue.pending;
  
  if (pending === null) {
    // 第一个 Update,指向自己形成环
    update.next = update;
  } else {
    // 插入到环中
    update.next = pending.next;
    pending.next = update;
  }
  
  // pending 始终指向最后一个 Update
  // pending.next 指向第一个 Update
  sharedQueue.pending = update;
}

// 处理 UpdateQueue
function processUpdateQueue(workInProgress, props) {
  const queue = workInProgress.updateQueue;
  let firstBaseUpdate = queue.firstBaseUpdate;
  let lastBaseUpdate = queue.lastBaseUpdate;
  
  // 获取待处理的 Update 链表
  let pendingQueue = queue.shared.pending;
  if (pendingQueue !== null) {
    queue.shared.pending = null;
    
    // 断开循环链表
    const lastPendingUpdate = pendingQueue;
    const firstPendingUpdate = lastPendingUpdate.next;
    lastPendingUpdate.next = null;
    
    // 将新的 Update 链接到 baseUpdate 后面
    if (lastBaseUpdate === null) {
      firstBaseUpdate = firstPendingUpdate;
    } else {
      lastBaseUpdate.next = firstPendingUpdate;
    }
    lastBaseUpdate = lastPendingUpdate;
  }
  
  // 处理所有 Update
  if (firstBaseUpdate !== null) {
    let newState = queue.baseState;
    let update = firstBaseUpdate;
    
    do {
      const updateLane = update.lane;
      
      // 根据优先级判断是否处理此 Update
      if (isSubsetOfLanes(renderLanes, updateLane)) {
        // 处理 Update
        newState = getStateFromUpdate(update, newState, props);
        
        // 如果有回调,加入 effects
        const callback = update.callback;
        if (callback !== null) {
          workInProgress.flags |= Callback;
          update.nextEffect = null;
          if (queue.effects === null) {
            queue.effects = [update];
          } else {
            queue.effects.push(update);
          }
        }
      }
      
      update = update.next;
      if (update === null) {
        break;
      }
    } while (true);
    
    queue.baseState = newState;
    queue.firstBaseUpdate = firstBaseUpdate;
    queue.lastBaseUpdate = lastBaseUpdate;
    
    workInProgress.memoizedState = newState;
  }
}

UpdateQueue 循环链表示意图:

graph LR
    A["pending"] --> B["Update 3"]
    B --> C["Update 1"]
    C --> D["Update 2"]
    D --> B
    
    E["firstBaseUpdate"] --> F["Update 4"]
    F --> G["Update 5"]
    G --> H["null"]
    
    style A fill:#ff6b6b
    style E fill:#51cf66
基本使用

栈是一种后进先出(LIFO)的数据结构。在 React 中,栈主要用于管理执行上下文和状态。

// 栈的基本实现
class Stack {
  constructor() {
    this.items = [];
  }
  
  // 入栈
  push(element) {
    this.items.push(element);
  }
  
  // 出栈
  pop() {
    if (this.isEmpty()) {
      return null;
    }
    return this.items.pop();
  }
  
  // 查看栈顶元素
  peek() {
    if (this.isEmpty()) {
      return null;
    }
    return this.items[this.items.length - 1];
  }
  
  // 判断栈是否为空
  isEmpty() {
    return this.items.length === 0;
  }
  
  // 获取栈大小
  size() {
    return this.items.length;
  }
  
  // 清空栈
  clear() {
    this.items = [];
  }
}

// 使用示例
const stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);

console.log(stack.peek()); // 3
console.log(stack.pop());  // 3
console.log(stack.pop());  // 2
console.log(stack.size()); // 1
React 中的应用
Context 状态管理

React 使用栈来管理 Context 的值,支持嵌套的 Context Provider。

// Context 值栈
const valueStack = [];
let index = -1;

// 创建 Context
function createContext(defaultValue) {
  const context = {
    $$typeof: REACT_CONTEXT_TYPE,
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    Provider: null,
    Consumer: null,
  };
  
  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };
  
  context.Consumer = context;
  
  return context;
}

// 入栈:保存当前值,设置新值
function pushProvider(providerFiber, nextValue) {
  const context = providerFiber.type._context;
  
  // 将当前值压入栈
  push(valueStack, context._currentValue);
  
  // 设置新值
  context._currentValue = nextValue;
}

// 出栈:恢复之前的值
function popProvider(providerFiber) {
  const context = providerFiber.type._context;
  
  // 从栈中弹出之前的值
  const currentValue = pop(valueStack);
  
  // 恢复之前的值
  context._currentValue = currentValue;
}

// 栈操作的辅助函数
function push(cursor, value) {
  index++;
  valueStack[index] = value;
}

function pop(cursor) {
  if (index < 0) {
    return;
  }
  
  const value = valueStack[index];
  valueStack[index] = null;
  index--;
  
  return value;
}

// 读取 Context 值
function readContext(context) {
  // 返回栈顶的值(当前生效的值)
  return context._currentValue;
}

Context 嵌套示例:

const ThemeContext = createContext('light');
const UserContext = createContext(null);

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <UserContext.Provider value={{ name: 'Alice' }}>
        <ThemeContext.Provider value="blue">
          <Child />
        </ThemeContext.Provider>
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

// Render 阶段,Context 值栈的变化:
// 1. pushProvider(ThemeContext, 'dark')
//    栈: ['light']
//    当前值: 'dark'
//
// 2. pushProvider(UserContext, { name: 'Alice' })
//    栈: ['light', null]
//    当前值: { name: 'Alice' }
//
// 3. pushProvider(ThemeContext, 'blue')
//    栈: ['light', null, 'dark']
//    当前值: 'blue'
//
// 4. 渲染 <Child />,读取 Context
//    ThemeContext._currentValue = 'blue'
//    UserContext._currentValue = { name: 'Alice' }
//
// 5. popProvider(ThemeContext)
//    栈: ['light', null]
//    恢复值: 'dark'
//
// 6. popProvider(UserContext)
//    栈: ['light']
//    恢复值: null
//
// 7. popProvider(ThemeContext)
//    栈: []
//    恢复值: 'light'

Context 栈的可视化:

graph TB
    subgraph "Context 栈的变化"
        A["初始状态<br/>栈: 空<br/>ThemeContext: light"] --> B["Provider value=dark<br/>栈: light<br/>ThemeContext: dark"]
        B --> C["Provider value=blue<br/>栈: light, dark<br/>ThemeContext: blue"]
        C --> D["渲染子组件<br/>读取 blue"]
        D --> E["popProvider<br/>栈: light, dark<br/>恢复为: dark"]
        E --> F["popProvider<br/>栈: light<br/>恢复为: light"]
    end
    
    style A fill:#61dafb
    style D fill:#ffd700
    style F fill:#51cf66

算法

diff 过程(调和)

Diff 算法是 React 协调(Reconciliation)过程的核心,用于比较新旧 Fiber 树,找出需要更新的节点,从而最小化 DOM 操作。

React Diff 算法的三个策略:

  1. 树层级比较:只比较同层级节点,不跨层级比较
  2. 组件类型比较:不同类型的组件会生成不同的树结构
  3. 列表比较:通过 key 标识列表中的元素
graph TB
    A["Diff 算法"] --> B["单节点 Diff"]
    A --> C["多节点 Diff"]
    
    B --> B1["key 和 type 都相同<br/>复用节点"]
    B --> B2["key 相同 type 不同<br/>删除旧节点 创建新节点"]
    B --> B3["key 不同<br/>删除旧节点 创建新节点"]
    
    C --> C1["第一轮遍历<br/>处理更新节点"]
    C --> C2["第二轮遍历<br/>处理剩余新节点"]
    C --> C3["第三轮遍历<br/>删除剩余旧节点"]
    
    style B fill:#61dafb
    style C fill:#ffd700

单节点 Diff 算法:

// 单节点 Diff:新节点只有一个子节点
function reconcileSingleElement(
  returnFiber,
  currentFirstChild,
  element
) {
  const key = element.key;
  let child = currentFirstChild;
  
  // 遍历旧的子节点
  while (child !== null) {
    // 1. 比较 key
    if (child.key === key) {
      // 2. key 相同,比较 type
      if (child.elementType === element.type) {
        // key 和 type 都相同,可以复用
        deleteRemainingChildren(returnFiber, child.sibling);
        
        const existing = useFiber(child, element.props);
        existing.ref = element.ref;
        existing.return = returnFiber;
        
        return existing;
      } else {
        // key 相同但 type 不同,删除所有旧节点
        deleteRemainingChildren(returnFiber, child);
        break;
      }
    } else {
      // key 不同,删除这个旧节点,继续比较下一个
      deleteChild(returnFiber, child);
    }
    
    child = child.sibling;
  }
  
  // 没有可复用的节点,创建新节点
  const created = createFiberFromElement(element);
  created.ref = element.ref;
  created.return = returnFiber;
  
  return created;
}

// 示例:单节点 Diff
// 旧:<div key="a">A</div>
// 新:<div key="a">B</div>
// 结果:复用 div 节点,更新内容

// 旧:<div key="a">A</div>
// 新:<span key="a">A</span>
// 结果:删除 div,创建新的 span

// 旧:<div key="a">A</div>
// 新:<div key="b">B</div>
// 结果:删除旧 div,创建新 div

多节点 Diff 算法:

// 多节点 Diff:新节点有多个子节点
function reconcileChildrenArray(
  returnFiber,
  currentFirstChild,
  newChildren
) {
  let resultingFirstChild = null;
  let previousNewFiber = null;
  
  let oldFiber = currentFirstChild;
  let lastPlacedIndex = 0;
  let newIdx = 0;
  let nextOldFiber = null;
  
  // 第一轮遍历:处理更新的节点
  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
    if (oldFiber.index > newIdx) {
      nextOldFiber = oldFiber;
      oldFiber = null;
    } else {
      nextOldFiber = oldFiber.sibling;
    }
    
    const newFiber = updateSlot(
      returnFiber,
      oldFiber,
      newChildren[newIdx]
    );
    
    if (newFiber === null) {
      // key 不同,跳出第一轮遍历
      if (oldFiber === null) {
        oldFiber = nextOldFiber;
      }
      break;
    }
    
    if (shouldTrackSideEffects) {
      if (oldFiber && newFiber.alternate === null) {
        // 新节点没有复用旧节点,删除旧节点
        deleteChild(returnFiber, oldFiber);
      }
    }
    
    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
    
    if (previousNewFiber === null) {
      resultingFirstChild = newFiber;
    } else {
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
    oldFiber = nextOldFiber;
  }
  
  // 新节点已经遍历完
  if (newIdx === newChildren.length) {
    // 删除剩余的旧节点
    deleteRemainingChildren(returnFiber, oldFiber);
    return resultingFirstChild;
  }
  
  // 旧节点已经遍历完
  if (oldFiber === null) {
    // 创建剩余的新节点
    for (; newIdx < newChildren.length; newIdx++) {
      const newFiber = createChild(returnFiber, newChildren[newIdx]);
      if (newFiber === null) {
        continue;
      }
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
    return resultingFirstChild;
  }
  
  // 第二轮遍历:处理移动的节点
  // 将剩余的旧节点放入 Map
  const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
  
  for (; newIdx < newChildren.length; newIdx++) {
    const newFiber = updateFromMap(
      existingChildren,
      returnFiber,
      newIdx,
      newChildren[newIdx]
    );
    
    if (newFiber !== null) {
      if (shouldTrackSideEffects) {
        if (newFiber.alternate !== null) {
          // 复用了旧节点,从 Map 中删除
          existingChildren.delete(
            newFiber.key === null ? newIdx : newFiber.key
          );
        }
      }
      
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
  }
  
  if (shouldTrackSideEffects) {
    // 删除 Map 中剩余的旧节点
    existingChildren.forEach(child => deleteChild(returnFiber, child));
  }
  
  return resultingFirstChild;
}

Diff 算法实例分析:

// 场景 1:节点更新
// 旧:[A, B, C]
// 新:[A, B, D]
// 操作:复用 A、B,删除 C,创建 D

// 场景 2:节点移动
// 旧:[A, B, C]
// 新:[C, A, B]
// 操作:移动 C 到前面,A、B 保持不变

// 场景 3:节点插入
// 旧:[A, C]
// 新:[A, B, C]
// 操作:复用 A、C,在中间插入 B

// 场景 4:节点删除
// 旧:[A, B, C]
// 新:[A, C]
// 操作:复用 A、C,删除 B

// 详细示例:节点移动
const oldChildren = [
  { key: 'a', index: 0 },
  { key: 'b', index: 1 },
  { key: 'c', index: 2 },
  { key: 'd', index: 3 }
];

const newChildren = [
  { key: 'd' },
  { key: 'a' },
  { key: 'b' },
  { key: 'c' }
];

// Diff 过程:
// 1. 第一轮遍历
//    - 比较 d 和 a,key 不同,跳出
//
// 2. 将剩余旧节点放入 Map
//    Map: { a: fiber_a, b: fiber_b, c: fiber_c, d: fiber_d }
//
// 3. 第二轮遍历
//    - 处理 d:在 Map 中找到,oldIndex=3, lastPlacedIndex=0
//      oldIndex(3) > lastPlacedIndex(0),不需要移动
//      更新 lastPlacedIndex = 3
//
//    - 处理 a:在 Map 中找到,oldIndex=0, lastPlacedIndex=3
//      oldIndex(0) < lastPlacedIndex(3),需要移动
//      标记 Placement
//
//    - 处理 b:在 Map 中找到,oldIndex=1, lastPlacedIndex=3
//      oldIndex(1) < lastPlacedIndex(3),需要移动
//      标记 Placement
//
//    - 处理 c:在 Map 中找到,oldIndex=2, lastPlacedIndex=3
//      oldIndex(2) < lastPlacedIndex(3),需要移动
//      标记 Placement
//
// 最终:d 不动,a、b、c 移动到 d 后面

Diff 算法流程图:

flowchart TD
    A["开始 Diff"] --> B{"节点数量"}
    B -->|单节点| C["单节点 Diff"]
    B -->|多节点| D["多节点 Diff"]
    
    C --> C1{"key 是否相同"}
    C1 -->|是| C2{"type 是否相同"}
    C2 -->|是| C3["复用节点"]
    C2 -->|否| C4["删除旧节点 创建新节点"]
    C1 -->|否| C4
    
    D --> D1["第一轮遍历<br/>更新节点"]
    D1 --> D2{"新节点遍历完?"}
    D2 -->|是| D3["删除剩余旧节点"]
    D2 -->|否| D4{"旧节点遍历完?"}
    D4 -->|是| D5["创建剩余新节点"]
    D4 -->|否| D6["第二轮遍历<br/>处理移动"]
    D6 --> D7["删除未复用的旧节点"]
    
    D3 --> E["结束"]
    D5 --> E
    D7 --> E
    C3 --> E
    C4 --> E
    
    style C3 fill:#51cf66
    style E fill:#61dafb
位运算
位运算基础

位运算是直接对二进制位进行操作的运算,执行速度极快。React 使用位运算来管理 Fiber 的标记(flags)和优先级(lanes)。

JavaScript 的位运算符:

// 1. 按位与(AND):&
//    两位都为 1 时结果为 1
5 & 3  // 0101 & 0011 = 0001 = 1

// 2. 按位或(OR):|
//    两位有一个为 1 时结果为 1
5 | 3  // 0101 | 0011 = 0111 = 7

// 3. 按位异或(XOR):^
//    两位不同时结果为 1
5 ^ 3  // 0101 ^ 0011 = 0110 = 6

// 4. 按位非(NOT):~
//    对每一位取反
~5  // ~0101 = 1010 = -6(补码)

// 5. 左移:<<
//    向左移动指定位数,右边补 0
5 << 1  // 0101 << 1 = 1010 = 10

// 6. 有符号右移:>>
//    向右移动指定位数,左边补符号位
5 >> 1  // 0101 >> 1 = 0010 = 2

// 7. 无符号右移:>>>
//    向右移动指定位数,左边补 0
-5 >>> 1  // 正数

位运算的常见技巧:

// 1. 判断奇偶
const isOdd = (n) => !!(n & 1);
isOdd(5);  // true
isOdd(6);  // false

// 2. 交换两个数(不使用临时变量)
let a = 5, b = 3;
a ^= b;  // a = 5 ^ 3
b ^= a;  // b = 3 ^ (5 ^ 3) = 5
a ^= b;  // a = (5 ^ 3) ^ 5 = 3
// 结果:a = 3, b = 5

// 3. 判断 2 的幂
const isPowerOfTwo = (n) => n > 0 && (n & (n - 1)) === 0;
isPowerOfTwo(8);   // true (1000 & 0111 = 0)
isPowerOfTwo(10);  // false

// 4. 获取最低位的 1
const getLowestBit = (n) => n & -n;
getLowestBit(12);  // 4 (1100 & 0100 = 0100)

// 5. 清除最低位的 1
const clearLowestBit = (n) => n & (n - 1);
clearLowestBit(12);  // 8 (1100 & 1011 = 1000)

// 6. 统计 1 的个数
function countOnes(n) {
  let count = 0;
  while (n) {
    n &= (n - 1);  // 每次清除最低位的 1
    count++;
  }
  return count;
}
countOnes(7);  // 3 (0111 有 3 个 1)
位运算求解

位运算在算法题中的应用非常广泛,可以高效地解决特定问题。

经典位运算问题:

// 1. 找出数组中唯一出现一次的数字(其他数字都出现两次)
function singleNumber(nums) {
  let result = 0;
  for (let num of nums) {
    result ^= num;  // 异或运算,相同的数会抵消
  }
  return result;
}

singleNumber([4, 1, 2, 1, 2]);  // 4
// 解释:4 ^ 1 ^ 2 ^ 1 ^ 2 = 4 ^ (1 ^ 1) ^ (2 ^ 2) = 4 ^ 0 ^ 0 = 4

// 2. 不使用加减乘除实现加法
function add(a, b) {
  while (b !== 0) {
    const carry = (a & b) << 1;  // 计算进位
    a = a ^ b;                    // 计算不带进位的和
    b = carry;                    // 将进位赋值给 b
  }
  return a;
}

add(5, 3);  // 8
// 解释:
// 5 = 0101, 3 = 0011
// 第一次:carry = 0010 << 1 = 0100, a = 0110, b = 0100
// 第二次:carry = 0100 << 1 = 1000, a = 0010, b = 1000
// 第三次:carry = 0000 << 1 = 0000, a = 1000, b = 0000
// 结果:1000 = 8

// 3. 数字范围按位与
function rangeBitwiseAnd(left, right) {
  let shift = 0;
  // 找到公共前缀
  while (left < right) {
    left >>= 1;
    right >>= 1;
    shift++;
  }
  return left << shift;
}

rangeBitwiseAnd(5, 7);  // 4
// 5 = 101, 6 = 110, 7 = 111
// 公共前缀:1,结果:100 = 4

// 4. 位 1 的个数(汉明重量)
function hammingWeight(n) {
  let count = 0;
  while (n !== 0) {
    count++;
    n &= (n - 1);  // 清除最低位的 1
  }
  return count;
}

hammingWeight(11);  // 3 (1011 有 3 个 1)
React 中的应用

React 在 Fiber 架构中大量使用位运算来管理标记(flags)和优先级(lanes),提高性能。

1. Fiber Flags(副作用标记)

// Fiber 的副作用标记(使用位运算)
const NoFlags = 0b0000000000000000000000000;
const PerformedWork = 0b0000000000000000000000001;
const Placement = 0b0000000000000000000000010;
const Update = 0b0000000000000000000000100;
const Deletion = 0b0000000000000000000001000;
const ChildDeletion = 0b0000000000000000000010000;
const ContentReset = 0b0000000000000000000100000;
const Callback = 0b0000000000000000001000000;
const Ref = 0b0000000000000000100000000;
const Snapshot = 0b0000000000000001000000000;
const Passive = 0b0000000000000010000000000;

// 添加标记
fiber.flags |= Placement;
fiber.flags |= Update;

// 检查是否有某个标记
const hasPlacement = (fiber.flags & Placement) !== 0;
const hasUpdate = (fiber.flags & Update) !== 0;

// 移除标记
fiber.flags &= ~Placement;

// 检查是否有多个标记之一
const hasPlacementOrUpdate = (fiber.flags & (Placement | Update)) !== 0;

// 示例:管理 Fiber 的副作用
function commitMutationEffects(fiber) {
  const flags = fiber.flags;
  
  // 检查是否有内容重置标记
  if (flags & ContentReset) {
    commitResetTextContent(fiber);
  }
  
  // 检查是否有 ref 更新
  if (flags & Ref) {
    const current = fiber.alternate;
    if (current !== null) {
      commitDetachRef(current);
    }
  }
  
  // 检查主要副作用
  const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
  
  switch (primaryFlags) {
    case Placement: {
      commitPlacement(fiber);
      // 清除 Placement 标记
      fiber.flags &= ~Placement;
      break;
    }
    case Update: {
      const current = fiber.alternate;
      commitWork(current, fiber);
      break;
    }
    // ... 其他情况
  }
}

2. Lanes(优先级模型)

// Lane 优先级(使用位运算)
const NoLanes = 0b0000000000000000000000000000000;
const SyncLane = 0b0000000000000000000000000000001;

const InputContinuousLane = 0b0000000000000000000000000000100;
const DefaultLane = 0b0000000000000000000000000010000;
const TransitionLanes = 0b0000000011111111111111111000000;
const IdleLane = 0b0100000000000000000000000000000;

// 检查是否包含某个 Lane
function includesSomeLane(a, b) {
  return (a & b) !== 0;
}

// 检查是否是 Lane 的子集
function isSubsetOfLanes(set, subset) {
  return (set & subset) === subset;
}

// 合并 Lanes
function mergeLanes(a, b) {
  return a | b;
}

// 移除 Lanes
function removeLanes(set, subset) {
  return set & ~subset;
}

// 获取最高优先级的 Lane
function getHighestPriorityLane(lanes) {
  return lanes & -lanes;  // 获取最低位的 1
}

// 示例:调度更新
function scheduleUpdateOnFiber(fiber, lane) {
  // 将 lane 合并到 fiber 的 lanes
  fiber.lanes = mergeLanes(fiber.lanes, lane);
  
  // 向上标记父节点的 childLanes
  let parent = fiber.return;
  while (parent !== null) {
    parent.childLanes = mergeLanes(parent.childLanes, lane);
    parent = parent.return;
  }
  
  // 调度根节点更新
  ensureRootIsScheduled(root);
}

// 处理更新时检查优先级
function beginWork(current, workInProgress, renderLanes) {
  const updateLanes = workInProgress.lanes;
  
  // 检查当前渲染是否包含这个 fiber 的更新
  if (!includesSomeLane(renderLanes, updateLanes)) {
    // 优先级不够,跳过这个 fiber
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }
  
  // 处理 fiber...
}

位运算优化对比:

// 传统方式:使用对象或数组管理标记
const flags = {
  placement: false,
  update: false,
  deletion: false
};

// 检查标记
if (flags.placement) { /* ... */ }

// 添加标记
flags.update = true;

// ❌ 问题:
// 1. 占用更多内存
// 2. 访问速度较慢
// 3. 难以批量操作

// React 方式:使用位运算
let flags = 0;

// 添加标记(一次操作)
flags |= Placement | Update;  // 0b110

// 检查多个标记(一次操作)
if (flags & (Placement | Update | Deletion)) { /* ... */ }

// 移除标记(一次操作)
flags &= ~Placement;

// ✅ 优势:
// 1. 内存占用小(只需一个数字)
// 2. 操作速度快(位运算是底层指令)
// 3. 可以轻松批量操作多个标记
图遍历
概念

图遍历是访问图中所有节点的过程。常见的图遍历算法有深度优先搜索(DFS)和广度优先搜索(BFS)。

图的基本概念:

// 图的表示方式
// 1. 邻接矩阵
const graph = [
  [0, 1, 1, 0],  // 节点 0 连接到节点 1、2
  [1, 0, 0, 1],  // 节点 1 连接到节点 0、3
  [1, 0, 0, 1],  // 节点 2 连接到节点 0、3
  [0, 1, 1, 0]   // 节点 3 连接到节点 1、2
];

// 2. 邻接表
const graph = {
  0: [1, 2],
  1: [0, 3],
  2: [0, 3],
  3: [1, 2]
};

// 3. 边列表
const edges = [
  [0, 1],
  [0, 2],
  [1, 3],
  [2, 3]
];
实现方式

深度优先搜索(DFS):

// DFS 递归实现
function dfsRecursive(graph, start, visited = new Set()) {
  console.log(start);  // 访问节点
  visited.add(start);
  
  const neighbors = graph[start] || [];
  for (const neighbor of neighbors) {
    if (!visited.has(neighbor)) {
      dfsRecursive(graph, neighbor, visited);
    }
  }
}

// DFS 迭代实现(使用栈)
function dfsIterative(graph, start) {
  const visited = new Set();
  const stack = [start];
  
  while (stack.length > 0) {
    const node = stack.pop();
    
    if (!visited.has(node)) {
      console.log(node);  // 访问节点
      visited.add(node);
      
      const neighbors = graph[node] || [];
      // 逆序入栈,保证遍历顺序
      for (let i = neighbors.length - 1; i >= 0; i--) {
        stack.push(neighbors[i]);
      }
    }
  }
}

// 示例
const graph = {
  0: [1, 2],
  1: [0, 3, 4],
  2: [0, 5],
  3: [1],
  4: [1],
  5: [2]
};

dfsRecursive(graph, 0);  // 输出:0, 1, 3, 4, 2, 5

广度优先搜索(BFS):

// BFS 实现(使用队列)
function bfs(graph, start) {
  const visited = new Set();
  const queue = [start];
  visited.add(start);
  
  while (queue.length > 0) {
    const node = queue.shift();
    console.log(node);  // 访问节点
    
    const neighbors = graph[node] || [];
    for (const neighbor of neighbors) {
      if (!visited.has(neighbor)) {
        visited.add(neighbor);
        queue.push(neighbor);
      }
    }
  }
}

// 带层级的 BFS
function bfsWithLevel(graph, start) {
  const visited = new Set();
  const queue = [[start, 0]];  // [节点, 层级]
  visited.add(start);
  
  while (queue.length > 0) {
    const [node, level] = queue.shift();
    console.log(`节点 ${node},层级 ${level}`);
    
    const neighbors = graph[node] || [];
    for (const neighbor of neighbors) {
      if (!visited.has(neighbor)) {
        visited.add(neighbor);
        queue.push([neighbor, level + 1]);
      }
    }
  }
}

// 示例
bfs(graph, 0);  // 输出:0, 1, 2, 3, 4, 5

DFS vs BFS 对比:

graph TB
    A[遍历算法] --> B[DFS 深度优先]
    A --> C[BFS 广度优先]
    
    B --> B1[特点]
    B1 --> B11[先深入到底]
    B1 --> B12[使用栈/递归]
    B1 --> B13["空间复杂度: O(height)"]
    
    B --> B2[应用场景]
    B2 --> B21[拓扑排序]
    B2 --> B22[检测环]
    B2 --> B23[路径查找]
    
    C --> C1[特点]
    C1 --> C11[逐层遍历]
    C1 --> C12[使用队列]
    C1 --> C13["空间复杂度: O(width)"]
    
    C --> C2[应用场景]
    C2 --> C21[最短路径]
    C2 --> C22[层序遍历]
    C2 --> C23[连通性检测]
    
    style B fill:#61dafb
    style C fill:#ffd700
React 中的应用
fiber 树的构造

Fiber 树的构建过程本质上是对虚拟 DOM 树的深度优先遍历(DFS)。

// Fiber 树的 DFS 遍历
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork) {
  const current = unitOfWork.alternate;
  
  // 1. beginWork:处理当前节点,返回子节点
  let next = beginWork(current, unitOfWork, renderLanes);
  
  if (next === null) {
    // 2. 没有子节点,执行 completeWork
    completeUnitOfWork(unitOfWork);
  } else {
    // 3. 有子节点,继续向下遍历
    workInProgress = next;
  }
}

function completeUnitOfWork(unitOfWork) {
  let completedWork = unitOfWork;
  
  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;
    
    // 完成当前节点的工作
    completeWork(current, completedWork, renderLanes);
    
    const siblingFiber = completedWork.sibling;
    
    if (siblingFiber !== null) {
      // 有兄弟节点,遍历兄弟节点
      workInProgress = siblingFiber;
      return;
    }
    
    // 没有兄弟节点,回到父节点
    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);
}

Fiber 树遍历顺序示例:

// JSX 结构
<App>
  <Header>
    <h1>Title</h1>
  </Header>
  <Content>
    <p>Text</p>
    <button>Click</button>
  </Content>
</App>

// DFS 遍历顺序(beginWork 阶段):
// 1. App (beginWork)
// 2. ├─ Header (beginWork)
// 3. │  └─ h1 (beginWork)
// 4. │     └─ "Title" (beginWork)
// 5. │     └─ "Title" (completeWork)
// 6. │  └─ h1 (completeWork)
// 7. ├─ Header (completeWork)
// 8. ├─ Content (beginWork)
// 9. │  ├─ p (beginWork)
// 10. │  │  └─ "Text" (beginWork)
// 11. │  │  └─ "Text" (completeWork)
// 12. │  ├─ p (completeWork)
// 13. │  ├─ button (beginWork)
// 14. │  │  └─ "Click" (beginWork)
// 15. │  │  └─ "Click" (completeWork)
// 16. │  ├─ button (completeWork)
// 17. ├─ Content (completeWork)
// 18. App (completeWork)

Fiber 树遍历流程图:

graph TD
    A["开始: workInProgress = rootFiber"] --> B["performUnitOfWork"]
    B --> C["beginWork 处理当前节点"]
    C --> D{"有子节点?"}
    D -->|是| E["workInProgress = child"]
    E --> B
    D -->|否| F["completeWork 完成当前节点"]
    F --> G{"有兄弟节点?"}
    G -->|是| H["workInProgress = sibling"]
    H --> B
    G -->|否| I{"有父节点?"}
    I -->|是| J["workInProgress = parent"]
    J --> F
    I -->|否| K["遍历完成"]
    
    style A fill:#61dafb
    style K fill:#51cf66
查找 context 的消费节点

当 Context 的值发生变化时,React 需要找到所有消费该 Context 的组件并触发更新。这是一个深度优先搜索过程。

// 查找并标记 Context 的消费者
function propagateContextChange(workInProgress, context, renderLanes) {
  let fiber = workInProgress.child;
  
  if (fiber !== null) {
    fiber.return = workInProgress;
  }
  
  // DFS 遍历子树
  while (fiber !== null) {
    let nextFiber;
    
    // 检查当前 fiber 是否消费了这个 Context
    const list = fiber.dependencies;
    if (list !== null) {
      nextFiber = fiber.child;
      
      let dependency = list.firstContext;
      while (dependency !== null) {
        // 找到消费了这个 Context 的 fiber
        if (dependency.context === context) {
          // 标记需要更新
          if (fiber.tag === ClassComponent) {
            // 创建 forceUpdate 的 Update
            const update = createUpdate(renderLanes);
            update.tag = ForceUpdate;
            enqueueUpdate(fiber, update);
          }
          
          // 将这个 fiber 的 lanes 合并到 renderLanes
          fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
          const alternate = fiber.alternate;
          if (alternate !== null) {
            alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
          }
          
          // 向上标记父节点的 childLanes
          scheduleContextWorkOnParentPath(
            fiber.return,
            renderLanes,
            workInProgress
          );
          
          // 标记依赖列表
          list.lanes = mergeLanes(list.lanes, renderLanes);
          
          break;
        }
        dependency = dependency.next;
      }
    } else if (fiber.tag === ContextProvider) {
      // 如果遇到同类型的 Provider,不需要继续向下查找
      nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
    } else {
      nextFiber = fiber.child;
    }
    
    if (nextFiber !== null) {
      // 有子节点,继续向下
      nextFiber.return = fiber;
    } else {
      // 没有子节点,查找兄弟或回到父节点
      nextFiber = fiber;
      while (nextFiber !== null) {
        if (nextFiber === workInProgress) {
          // 回到了起点,搜索结束
          nextFiber = null;
          break;
        }
        const sibling = nextFiber.sibling;
        if (sibling !== null) {
          sibling.return = nextFiber.return;
          nextFiber = sibling;
          break;
        }
        nextFiber = nextFiber.return;
      }
    }
    
    fiber = nextFiber;
  }
}

// 使用示例
const ThemeContext = React.createContext('light');

function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={theme}>
      <Header />
      <Content>
        <Sidebar />
        <Main />
      </Content>
    </ThemeContext.Provider>
  );
}

function Header() {
  const theme = useContext(ThemeContext);  // 消费 Context
  return <header className={theme}>Header</header>;
}

function Main() {
  const theme = useContext(ThemeContext);  // 消费 Context
  return <main className={theme}>Main</main>;
}

// 当 theme 变化时:
// 1. propagateContextChange 从 Provider 开始 DFS
// 2. 遍历 Header、Content、Sidebar、Main
// 3. 找到 Header 和 Main 消费了 ThemeContext
// 4. 标记 Header 和 Main 需要更新
// 5. 向上标记所有祖先节点的 childLanes

Context 消费者查找流程:

graph TD
    A["Context.Provider 值变化"] --> B["propagateContextChange"]
    B --> C["DFS 遍历子树"]
    C --> D{"检查当前 Fiber"}
    D -->|有 dependencies| E{"是否消费此 Context?"}
    E -->|是| F["标记 Fiber 需要更新"]
    E -->|否| G["继续遍历"]
    D -->|无 dependencies| G
    D -->|是 Provider| H{"是否同类型?"}
    H -->|是| I["停止向下查找"]
    H -->|否| G
    F --> J["向上标记 childLanes"]
    J --> G
    G --> K{"有子节点?"}
    K -->|是| C
    K -->|否| L{"有兄弟节点?"}
    L -->|是| C
    L -->|否| M{"回到父节点"}
    M --> L
    
    style A fill:#61dafb
    style F fill:#ffd700
    style J fill:#51cf66