2-2 React设计思想与优势
前端技术的演进脉络
要理解 React 的设计思想,需要先看清楚前端技术演进的历史脉络。
jQuery 时代
十年前的前端页面以 HTML 和 CSS 为主,JavaScript 在 Ajax 技术出现后才开始流行。jQuery 带来了一场行业变革——它让开发者摆脱了不同浏览器之间的 API 兼容困扰,通过简洁的选择器和链式调用控制页面 DOM,创造出丰富的交互效果。但随着项目复杂度增长,jQuery 的"命令式 DOM 操作"逐渐暴露出可维护性差的问题。
AngularJS 时代
AngularJS(Angular 1)是一个划时代的产品,它带来了 MVC 设计思想和双向数据绑定的概念。但双向数据绑定也成为了它的天生缺陷——随着页面复杂度和组件层级的增加,性能急剧下降,状态管理变得混乱。最终连 Google 自己都抛弃了 Angular 1,推出了完全重写的 Angular 2+。
三大框架并存
Angular 1 的经验教训催生了现代前端三大框架,它们都吸收了组件化思想,但在设计哲学上各有侧重:
| 维度 | React | Vue | Angular |
|---|---|---|---|
| 核心理念 | UI = f(data) | 渐进式框架 | 完整企业级方案 |
| 数据流 | 单向数据流 | 双向绑定(可配置) | 双向绑定 |
| 模板方案 | JSX(纯 JS) | HTML 模板 + 指令 | HTML 模板 + 指令 |
| 状态管理 | 单一状态树 | 响应式代理 | 服务注入 |
| 学习曲线 | 中等 | 平缓 | 陡峭 |
| 国际口碑 | 极高 | 高 | 中等 |
| 国内使用 | 大厂主流 | 中小团队主流 | 较少 |
React 的三大设计哲学
1. 单向数据流
React 采取了与 Angular 截然不同的策略——单向数据流。它的核心思想是:UI 就是数据到视图的映射函数,相同的输入永远产生相同的输出。
这种可预测性带来了几个好处:
- 调试方便——数据流向清晰,问题定位容易
- 测试简单——纯函数的特性让单元测试变得直观
- 性能可控——React 可以精确判断哪些组件需要更新
2. 虚拟 DOM
虚拟 DOM 的设计灵感来自容器化技术和虚拟化技术中的"快照"思想。它在真实 DOM 和 UI 层之间加了一层抽象:
状态变化 → 生成新虚拟 DOM → 与旧虚拟 DOM 对比(Diff) → 最小化真实 DOM 更新
text
这样做的核心目的是减少页面渲染次数——直接操作 DOM 会触发浏览器的重排重绘,代价高昂;而通过虚拟 DOM 的 Diff 算法,可以计算出最小化的 DOM 操作集。
3. 组件化
组件化思想虽然在 Angular 时代就已出现,但真正将其发扬光大的是 React。React 的组件化有几个关键特征:
- 独立:每个组件可以自由组合、替换,甚至可以跨项目复用
- 统一:所有组件遵循同一套规则(props 进、事件出)
- 积木式:复杂应用由无数小组件拼接搭建
组件化的目标是把网站中的元素尽可能独立出来,实现产品一致性(日期组件、输入组件在不同地方样式统一),方便团队协作,提升系统的可维护性。
React 核心开发者十大设计思想
React 核心开发者 Sebastian Markbåge 在 2016 年的一篇文章中阐述了 React 的基础设计思想。这些概念在今天看来依然深刻影响着 React 的发展方向。
1. 变换(Transformation)
设计 React 的核心前提:UI 就是把数据通过映射关系变成另一种形式的数据。这与单向数据流的思想一脉相承——相同的输入产生相同的输出,UI 是状态的函数。
2. 抽象(Abstraction)
不可能用一个函数实现复杂的 UI,需要把 UI 抽象为多个隐藏内部细节且可复用的函数。React 中的组件就是这种抽象的体现。
3. 组合(Composition)
组合是抽象之上的抽象。把通用的逻辑提炼成独立的容器(如 FancyBox),在任何需要的地方调用,实现逻辑的分离和复用——这就是我们常说的解耦。
4. 状态(State)
静态的 UI 没有意义,真正的应用需要与用户交互。React 的做法是将可变的 state 与不可变的函数模型分离:
function LikeButton() {
const [likes, setLikes] = useState(0);
return <button onClick={() => setLikes(likes + 1)}>Likes: {likes}</button>;
}
jsx
状态的修改通过专门的函数(如 setLikes)触发,React 自动重新渲染视图。
5. 缓存/持久化(Memoization)
每次状态变化都重新渲染整个页面会带来严重的性能问题。Memoization 的核心是记录函数的参数和结果的映射关系——如果参数没变,直接返回缓存结果,跳过渲染。这正是 React.memo、useMemo、useCallback 的底层思想。
6. 列表(List)
大部分 UI 的核心展示形态都是列表。React 推崇用 map() 函数处理列表渲染,这种思维方式更贴近原生 JavaScript,而非 Angular/Vue 的模板指令。
7. 连续性(Continuity)
将列表逻辑进一步抽离成独立的可复用组件(如 FancyUserList),在不同场景下复用同一套列表渲染逻辑。
8. 状态映射(State Mapping)
父组件可以将 state 传递给子组件(即 React 中的 props),形成自上而下的数据流。
9. 缓存映射(Memoization Mapping)
与状态映射类似,但传递的是缓存逻辑而非状态本身。
10. 代数效应(Algebraic Effects)
当代数效应解决的核心问题是:状态和缓存的逐级传递会导致参数爆炸(prop drilling)。解决方案是使用公共的上下文(Context)或 Hooks,将状态管理从组件树的层级传递中抽离出来,放在独立的位置集中管理。
Props 逐级传递(问题) → Context / Hooks(解决方案)
A → B → C → D → E → A → E(跳过中间层)
text
这十大设计思想与 React 现有 API 的对应关系:
| 设计思想 | React 中的体现 |
|---|---|
| 变换 | UI = f(state) |
| 抽象 | 组件(Component) |
| 组合 | 组件嵌套、HOC、Render Props |
| 状态 | useState、useReducer |
| 缓存 | React.memo、useMemo、useCallback |
| 列表 | Array.map() |
| 连续性 | 列表组件抽离 |
| 状态映射 | Props |
| 缓存映射 | useMemo 传递 |
| 代数效应 | Context、Hooks |
React 的实战优势
用一段对比代码来说明 React 的可读性优势:
Angular/Vue 模板写法:
<div ng-repeat="todo in todos">
<span ng-bind="todo.text"></span>
</div>
html
React JSX 写法:
{todos.map(todo => (
<span key={todo.id}>{todo.text}</span>
))}
jsx
对于没有学过 Vue 或 Angular 的人来说,ng-repeat 和 v-for 需要额外的学习成本;而 React 的 map() 是标准 JavaScript 方法,只要懂 JS 就能看懂。这就是 React 的核心理念——尽量贴近原生 JavaScript,降低概念门槛。
React 官方的口号"Learn Once, Write Anywhere"也正是基于这种设计哲学:一次学习 React 的核心概念,就可以在 Web(React)、移动端(React Native)、桌面端(Electron)、服务端(Next.js/Remix)等多个场景中复用。
↑