MD 更新:2026/6/2

React

[!abstract] 一句话定义 React 是一个声明式 UI 库——你描述”界面应该长什么样”,React 负责高效地把真实 DOM 更新成你描述的样子。

为什么需要它?

2013 年之前,前端开发的主流方式是 jQuery 手动操作 DOM

// 用户点击按钮 → 手动找到元素 → 手动更新内容
$('#counter').text(count + 1);
$('#list').append('<li>' + newItem + '</li>');

这在小型页面上没问题。但当页面复杂到有上百个状态、上千个 DOM 节点时,噩梦开始了:

  • 状态 A 变了 → 需要手动更新节点 1、3、7、12……漏了一个就出 bug
  • 状态 B 也影响节点 3 → 更新顺序搞错 → 界面闪烁
  • 团队协作时谁也不知道某个 DOM 节点被哪些状态影响 → 改一处坏三处

核心痛点:命令式编程让你管理”怎么做”,但你的大脑根本管不过来。

React 的解法很简单:别管怎么做了,告诉我结果应该是什么。 你只需声明 UI = f(state)——给定状态,界面是什么样子。状态变了,React 自动算出最小 DOM 更新并执行。

核心直觉

把 React 想象成一个智能助手

没有 React(命令式):你自己拿蓝图去工地搬砖。改了一面墙,你得想清楚承重墙能不能动、水电管线怎么改、地板要不要重铺——每一步都自己动手。

有 React(声明式):你只管画蓝图。每次改设计图,助手自动算出最小改动方案(拆这面墙、补那根管),然后去执行。你不关心施工细节,只关心最终效果。

关键洞察:React 不是框架,而是渲染引擎。它不告诉你怎么路由、怎么发请求、怎么管理全局状态——它只专注一件事:状态变了,高效更新界面。路由、数据获取、状态管理等由生态(React Router、TanStack Query、Zustand 等)补充。

它是怎么工作的?

渲染流程:从状态到像素

flowchart LR
    subgraph "Render 阶段 — 可中断"
        STATE["State / Props 变化"] --> RENDER["组件函数执行<br/>生成 React Element 树"]
        RENDER --> VDOM["Virtual DOM<br/>新 Element 树"]
        VDOM --> DIFF["Fiber Reconciler<br/>Diff 新旧树"]
    end

    subgraph "Commit 阶段 — 不可中断"
        DIFF -->|"最小变更集"| COMMIT["Commit 阶段<br/>批量更新真实 DOM"]
        COMMIT --> LAYOUT["Layout Effects<br/>useLayoutEffect 执行"]
        LAYOUT --> PAINT["浏览器 Paint"]
        PAINT --> EFFECTS["Effects<br/>useEffect 执行"]
    end

架构师注释:React 的渲染严格分为两个阶段。Render 阶段是纯计算(生成新虚拟 DOM + Diff),可以被中断和恢复——这是 React 18+ 并发渲染的基础。Commit 阶段是真实 DOM 操作,不可中断——一旦开始就必须完成,保证用户看到的界面始终一致。

Fiber:React 的内部引擎

React 16(2017)引入了 Fiber 架构,彻底重写了协调算法。一个 Fiber 就是一个 JavaScript 对象,代表一个组件实例和一个工作单元:

// 简化版 Fiber 结构
{
  type: 'div',              // 组件类型
  key: null,                // 列表中的 key
  props: { className: '...' }, // 属性
  stateNode: domNode,       // 真实 DOM 节点

  // 树结构:父子兄弟链表
  return: parentFiber,      // 父节点
  child: firstChildFiber,   // 第一个子节点
  sibling: nextFiber,       // 右兄弟节点

  // 双缓冲:current 与 workInProgress
  alternate: otherTreeFiber, // 指向另一棵树
  flags: Placement | Update, // 需要执行的操作
}

为什么用链表而不用树? 因为链表可以增量遍历——React 可以暂停当前工作,处理更高优先级的更新,然后再回来继续。旧的栈式协调器做不到这一点。

双缓冲机制:Current 树与 WorkInProgress 树

graph LR
    subgraph "屏幕上显示的"
        CURRENT["Current 树<br/>对应当前 DOM 状态"]
    end

    subgraph "内存中构建的"
        WIP["WorkInProgress 树<br/>新状态下的 Fiber 树"]
    end

    CURRENT -->|"alternate"| WIP
    WIP -->|"alternate"| CURRENT

    WIP -->|"commit 阶段完成"| SWAP["指针切换<br/>WIP 变成新 Current"]
    SWAP --> CURRENT

React 同时维护两棵 Fiber 树。Current 树对应屏幕上当前显示的 DOM 状态,WorkInProgress 树是正在内存中构建的新树。当 WIP 树构建完成并 commit 到 DOM 后,两棵树的角色互换——WIP 变成新的 Current。这叫双缓冲,和游戏引擎的双缓冲渲染是同一个原理:用户永远看不到半成品。

并发渲染(React 18+)

Fiber 架构最重要的能力就是可中断渲染

sequenceDiagram
    participant UI as 用户交互
    participant React as React 调度器
    participant Render as Render 阶段
    participant Commit as Commit 阶段

    Note over Render: 低优先级更新开始(如列表过滤)
    Render->>Render: 处理了 30%...
    UI->>React: 高优先级事件!(用户输入)
    React->>Render: 中断当前渲染
    Note over Render: 丢弃低优先级 WIP
    React->>Render: 开始高优先级渲染
    Render->>Commit: 高优先级 commit
    Note over Commit: 用户输入立即反映
    React->>Render: 恢复低优先级更新(从0重新开始)
    Render->>Commit: 低优先级 commit

这意味着:当用户在输入框打字时,React 可以中断一个大型列表的重新渲染,优先处理输入框更新,让用户感觉不到卡顿。

Hooks:让函数组件拥有状态

React 16.8(2019)引入 Hooks,是 React 哲学的分水岭:

// 之前:类组件 —— this 指向混乱,生命周期割裂逻辑
class Counter extends React.Component {
  state = { count: 0 };
  componentDidMount() { /* 订阅 */ }
  componentDidUpdate() { /* 同步 */ }
  componentWillUnmount() { /* 清理 */ }
  render() { return <button>{this.state.count}</button>; }
}

// 之后:函数组件 + Hooks —— 逻辑按功能组织,而非按生命周期
function Counter() {
  const [count, setCount] = useState(0);          // 状态
  useEffect(() => { /* 订阅 + 清理 */ return cleanup; }, [count]); // 副作用
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Hooks 的本质是把组件从”类”变成了”函数”——组件就是一个纯函数 UI = f(state)。状态、副作用、上下文都通过 Hook 函数”挂”到组件上,而不是继承自一个基类。

React 19 的三大新特性

特性解决什么问题核心思路
Server Components (RSC)客户端 JS 包太大、数据获取瀑布流组件在服务端执行,只发送 HTML 到客户端,减少 JS 体积
Actions + use server表单提交/数据变更需要大量样板代码<form action={serverFn}> 一行搞定,自动处理加载态/错误/乐观更新
React Compiler手动 useMemo/useCallback/memo 易遗漏编译时自动优化,减少 25-40% 不必要的重渲染

关键组件 / 核心要素

概念作用类比
JSX在 JS 中写类 HTML 语法,编译后生成 React Element 对象蓝图的速记符号——写起来像 HTML,本质是 JS 对象
Component可复用的 UI 单元,接收 props,返回 UI 描述蓝图中的标准零件——定义一次,到处复用
Props父组件向子组件传递数据的只读通道订单——客户(父组件)向工厂(子组件)下的规格要求
State组件内部的私有可变数据,变化触发重新渲染工厂的内部库存——客户看不到,但库存变了产品就变
Virtual DOM真实 DOM 的轻量 JS 对象表示,用于 Diff 计算草稿纸——先在草稿上画好修改方案,再去真实画布上执行
FiberReact 内部的工作单元和链表节点流水线工位——每个工位处理一个组件,可暂停可恢复
ReconcilerDiff 新旧 Fiber 树,计算出最小变更集差异分析师——对比新旧蓝图,找出最小施工方案
Scheduler为更新分配优先级,决定执行顺序急诊分诊台——按紧急程度排列任务
Hooks让函数组件使用状态、副作用等能力的 API接口插槽——纯函数没有状态,但通过插槽可以”挂”上各种能力
Context跨组件层级传递数据,避免 props 逐层传递广播系统——不用逐层传递,直接广播给所有订阅者
Suspense声明式等待异步数据加载,显示 fallback UI加载占位符——数据没来先显示骨架屏

与相关概念的关系

[!info] vs Vue 两者同为声明式 UI 框架,但设计哲学不同:

维度ReactVue
思维模型UI = f(state),纯函数 + 不可变数据响应式代理,可变数据自动触发更新
模板 vs JSXJSX(JS 中写 UI)模板(HTML 中写逻辑)
状态管理useState/useReducer,手动优化ref/reactive,自动追踪依赖
学习曲线函数式思维,Hooks 规则需要理解渐进式,选项式 API 更直觉
灵活性极高(只有 UI 层)适中(官方提供路由/状态管理等)
生态规模更大(npm 下载量 ~2x)更集中(官方维护完整方案)

[!note] vs Bun / Node.js React 本身是纯前端 UI 库,不依赖特定运行时。但 SSR(服务端渲染)和 Server Components 需要服务端运行时——Next.js 默认使用 Node.js,但也可以用 Bun 作为运行时。

[!tip] 被 Next.js 使用 Next.js 是 React 生态中最主流的全栈框架,它在 React 之上提供了路由、SSR/SSG、Server Components 支持、API 路由等能力。React 19 的 Server Components 特性目前主要通过 Next.js App Router 来使用。

[!note] 依赖于 JavaScript / TypeScript React 的运行时用 JavaScript 编写,组件也是 JS/TS 函数。理解 JavaScript 的闭包、事件循环、Promise 对于理解 React 行为至关重要。

典型应用场景

  • 单页应用(SPA) — React 最经典的场景:Dashboard、管理后台、SaaS 产品等交互密集型应用
  • 服务端渲染应用(SSR) — 通过 Next.js 等框架,React 组件在服务端渲染为 HTML,首屏更快、SEO 友好
  • 跨平台应用 — React Native 用相同的组件模型开发 iOS/Android 原生应用
  • 交互式文档/网站 — Docusaurus、Nextra 等静态站点生成器基于 React
  • 复杂表单/数据密集界面 — 配合 TanStack Query / React Hook Form 等生态库处理复杂交互

常见误解与陷阱

[!danger] ❌ 误以为:Virtual DOM 比 直接操作 DOM 快 ✅ 实际上:Virtual DOM 的优势不是”更快”,而是在提供声明式编程模型的同时,性能可接受。精心优化的原生 DOM 操作永远比 Virtual DOM Diff 快。但 Virtual DOM 让你不需要手动优化——“够快”比”最快但难以维护”更重要。React Compiler 的引入正是为了自动减少不必要的 Diff。

[!danger] ❌ 误以为:React 是一个完整的框架 ✅ 实际上:React 是一个 UI 库,只负责”状态到 UI”的映射。路由(React Router)、数据获取(TanStack Query)、状态管理(Zustand/Jotai)、表单(React Hook Form)全部由第三方生态提供。这给了极大灵活性,但也意味着新手面对”选什么库”会困惑。对比 Vue 官方提供全家桶的方式,React 是”自助餐”,Vue 是”套餐”。

[!danger] ❌ 误以为:Hooks 可以在 if/for 中调用 ✅ 实际上:Hooks 必须在组件顶层调用,不能在条件语句、循环或嵌套函数中调用。原因是 React 通过调用顺序来追踪哪个 Hook 对应哪个状态——如果顺序变了,状态就乱了。这是 Hooks 最重要的规则(Rules of Hooks)。

[!danger] ❌ 误以为:useState 更新是立即生效的 ✅ 实际上:setState异步的——调用后 state 不会立即改变,而是在下一次渲染时生效。多次 setState 调用会被批量合并(batching)。如果需要基于前一个 state 更新,使用函数式写法:setCount(c => c + 1)

[!danger] ❌ 误以为:Server Components 意味着放弃交互性 ✅ 实际上:Server Components 和 Client Components 是混合使用的。Server Components 负责数据获取和静态内容(减少客户端 JS),Client Components 负责交互逻辑(useState、事件处理等)。整个页面可以同时包含两种组件,React 在服务端渲染 Server Components,在客户端 hydrate Client Components。

延伸阅读

  • 想深入理解 Fiber 架构 → 阅读 React 源码中 react-reconciler 包,理解 workLoopbeginWorkcompleteWork 三个核心函数的工作流
  • 想理解并发渲染原理 → 研究 Scheduler 的优先级分配(ImmediatePriority > UserBlockingPriority > NormalPriority > LowPriority > IdlePriority)和 shouldYield() 机制
  • 想看工程实践 → 用 Next.js App Router 构建一个 Server Components + Client Components 混合的应用,理解 'use client''use server' 的边界
  • 想了解 React Compiler → 它基于 Babel 插件在编译时分析组件依赖,自动插入 memoization 逻辑,目标是让开发者不再需要手动写 useMemo/useCallback/React.memo

关联概念

前置知识JavaScript · TypeScript · DOM 同族概念Vue · Svelte · Angular 运行时Node.js · Bun 生态框架Next.js · React Native · Remix 核心机制Virtual DOM · Fiber 架构 · Hooks · Server Components 相关范式声明式编程 · 函数式编程 · 响应式编程