Skip to content
核心概念

核心概念

本章深入 React 的核心机制:JSX 编译产物、Virtual DOM、Fiber 调和(Reconciliation),以及渲染与提交分离的流程。


JSX 的本质

JSX 是 React.createElement() 的语法糖。Babel 或 Vite 的 React 插件在构建时做转换:

// 手写的 JSX
<div className="box">
  <h1>Hello</h1>
</div>

// 编译后 ≈
import { jsx as _jsx } from 'react/jsx-runtime';
_jsx('div', { className: 'box', children: _jsx('h1', children: 'Hello') });

🔬 深入原理:React 17+ 引入了新的 JSX 转换(react/jsx-runtime),不再需要手动 import React from 'react'。编译器自动注入 jsx / jsxs 函数。

JSX 的编译产物是一个普通的 JavaScript 对象(React Element):

// 一个 React Element 的形状
{
  $$typeof: Symbol(react.element),
  type: 'div',          // 原生标签 → 字符串;自定义组件 → 函数引用
  key: null,
  ref: null,
  props: {
    className: 'box',
    children: {
      $$typeof: Symbol(react.element),
      type: 'h1',
      props: { children: 'Hello' }
    }
  }
}

React Element vs Component vs DOM

概念 是什么 由谁创建 何时生成
React Element 轻量 JS 对象,描述 UI createElement / JSX render 时
Component 函数(或 class),接收 props 返回 Element 开发者 定义时
Fiber React 内部的工作单元节点 React 调和阶段
DOM Node 浏览器真实节点 浏览器 提交阶段

关键区别:Component 是蓝图,Element 是图纸,DOM 是建筑。


Virtual DOM 与 Reconciliation

什么是 Virtual DOM

Virtual DOM 是一个轻量的 JavaScript 对象树,它是真实 DOM 的抽象表示。React 通过对比新旧两棵 Virtual DOM 树(Diffing),计算出最小的 DOM 操作。

调和流程(Reconciliation)

State 变化
  │
  ▼
标记需要更新的 Fiber 节点
  │
  ▼
【Render 阶段】构建新的 Fiber 树,与当前树做 Diff
  │
  ▼
【Commit 阶段】将差异批量应用到真实 DOM
  │
  ▼
浏览器重绘

两个阶段分离的意义:Render 阶段可以被中断、恢复、丢弃(异步可中断),Commit 阶段必须同步完成。

🔬 深入原理:Render 阶段走的是 Fiber 树的"替代"分支(workInProgress),所有计算都在内存中完成。Commit 阶段才一次性修改 DOM,用户不会看到中间状态。


Fiber 架构

Fiber(纤维)是 React 16+ 的调和引擎核心。每个组件实例对应一个 Fiber Node。

Fiber Node 的关键字段

{
  tag:    FunctionComponent,  // 节点类型
  stateNode: 组件实例,
  child:    第一个子节点,
  sibling:  下一个兄弟节点,
  return:   父节点,
  alternate:  对应的 workInProgress / current,
  memoizedState:  当前 hooks 链表,
  pendingProps:   即将处理的 props,
  memoizedProps:  上次渲染的 props,
  effectTag:   副作用标记(Placement/Update/Deletion)
}

Fiber 树的双缓冲

React 维护两颗 Fiber 树:

  • current:对应当前屏幕上的 UI
  • workInProgress(WIP):正在内存中构建的新树

Diff 完成后,WIP 树切换为新的 current(指针交换,O(1))。这就是 React 的"双缓冲"机制。

工作循环(Work Loop)

┌───────────────────────────────┐
│ beginWork() — 递阶段          │
│   遍历 Fiber 树(DFS)         │
│   对比新旧 props,标记副作用    │
│   可被 shouldYield() 中断      │
└──────────────┬────────────────┘
               │ (子节点完成)
               ▼
┌───────────────────────────────┐
│ completeWork() — 归阶段        │
│   收集副作用链                  │
│   创建/更新 DOM 节点            │
└──────────────┬────────────────┘
               │ (归到根节点)
               ▼
┌───────────────────────────────┐
│ Commit Phase — 提交阶段        │
│   BeforeMutation → Mutation   │
│   → Layout (useLayoutEffect)  │
└───────────────────────────────┘

Diffing 算法(三点策略)

React 的 Diff 基于三点启发式假设(O(n) 复杂度):

1. 类型不同 → 整棵树重建

// old
<div><span>Hello</span></div>
// new
<header><span>Hello</span></header>

// 结果:div 及其子节点全部销毁,header 及其子节点全部新建
// 即使 span 内容相同也不会保留

2. key 标识列表节点

// 不用 key(索引当作 key):低效
{todos.map((todo, i) => <Todo key={i} {...todo} />)}

// 用稳定 id:正确
{todos.map(todo => <Todo key={todo.id} {...todo} />)}

🚨 陷阱:用数组索引做 key 在列表顺序变化时会导致组件状态错位、不必要的 DOM 更新甚至 input 焦点错乱。

3. 同一子树内只同层比较

React 不会跨层级比较节点。如果节点从一层移到另一层,React 会销毁后再重建,而非移动。


State 的批处理(Batching)

React 18+ 会对所有状态更新自动批处理:

function handleClick() {
  setCount(c => c + 1);   // 不会立即触发渲染
  setFlag(f => !f);       // 不会立即触发渲染
  // React 将两次更新合并为一次渲染
}

// 异步回调中也会批处理(React 18+)
async function handleAsync() {
  const data = await fetch('/api');
  setData(data);          // 与下面的 setLoading 合并
  setLoading(false);      // 只触发一次渲染
}

🔬 深入原理:React 18 前,只在事件处理器内批处理。异步回调(setTimeout、Promise)中的多次 setState 各触发一次渲染。React 18 引入 createRoot 后全部自动批处理。

需要跳过批处理(罕见)

import { flushSync } from 'react-dom';

flushSync(() => {
  setCount(c => c + 1);  // 立即提交到 DOM
});
// 此时 DOM 已更新

渲染 ≠ 提交到 DOM

这个区分是理解 React 性能的核心:

阶段 执行内容 可中断 副作用
Render 调用组件函数,计算差异 ✅ 可中断 不应包含副作用
Commit 修改 DOM、触发 ref 回调 ❌ 同步 副作用执行时机
function Component() {
  console.log('Render 阶段 — 可能被多次调用(StrictMode 下确如此)');
  
  useEffect(() => {
    console.log('Commit 阶段 — 只在 DOM 提交后执行一次');
  });
}

💡 最佳实践:不要在组件函数体中做副作用(修改全局变量、发请求、操作 DOM)。所有副作用都放入 useEffect 或事件处理器。


核心概念速查表

概念 一句话解释
JSX 返回 React Element 的 JavaScript 扩展语法
Virtual DOM 真实 DOM 的 JS 对象抽象层
Fiber 每个组件实例对应的内部工作单元
Reconciliation React 对比新旧树并决定最小 DOM 操作的流程
Render Phase 组件函数执行 + Diffing,可中断
Commit Phase DOM 更新 + 副作用执行,不可中断
Batching 合并多个 setState 为一次渲染
Key 列表 Diff 优化的标识,必须稳定唯一