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 计算 | 草稿纸——先在草稿上画好修改方案,再去真实画布上执行 |
| Fiber | React 内部的工作单元和链表节点 | 流水线工位——每个工位处理一个组件,可暂停可恢复 |
| Reconciler | Diff 新旧 Fiber 树,计算出最小变更集 | 差异分析师——对比新旧蓝图,找出最小施工方案 |
| Scheduler | 为更新分配优先级,决定执行顺序 | 急诊分诊台——按紧急程度排列任务 |
| Hooks | 让函数组件使用状态、副作用等能力的 API | 接口插槽——纯函数没有状态,但通过插槽可以”挂”上各种能力 |
| Context | 跨组件层级传递数据,避免 props 逐层传递 | 广播系统——不用逐层传递,直接广播给所有订阅者 |
| Suspense | 声明式等待异步数据加载,显示 fallback UI | 加载占位符——数据没来先显示骨架屏 |
与相关概念的关系
[!info] vs Vue 两者同为声明式 UI 框架,但设计哲学不同:
维度 React Vue 思维模型 UI = f(state),纯函数 + 不可变数据响应式代理,可变数据自动触发更新 模板 vs JSX JSX(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包,理解workLoop、beginWork、completeWork三个核心函数的工作流 - 想理解并发渲染原理 → 研究 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 相关范式:声明式编程 · 函数式编程 · 响应式编程