让我们先把一件事说清楚: React 不是游戏引擎。 它不会像一只喝了咖啡的松鼠一样, 每 16 毫秒就醒来检查是否需要重绘 UI。那样做会极其浪费资源——而 React 恰恰不是这样设计的。
但问题是: 有时候你_感觉_它就是这样。你做了一个小改动, 突然发现你的组件又渲染了。又一次。再一次。那么 React 底层到底是怎么工作的?你又该在什么时候使用 useMemo, useCallback 或 React.memo 这样的工具呢?
这篇文章将完整讲解 React _实际_在做什么, 记忆化的原理, 以及为什么浅比较依然有成本——即使它很低。
React 是事件驱动的——不是帧驱动的
当人们听到"响应式 UI"时, 常常会想象成某种循环, 比如这样:
setInterval(() => {
renderEverything(); // 假想的 React
}, 16); // ~60 FPS
这就是游戏循环的工作方式——但对于现代 Web UI 来说, 这样做会非常糟糕。React 并不是这样运作的。
相反, React 会等待。它是事件驱动的:
onClick(() => setState(...));
// 然后 React 才说: "哦!该渲染了。"
在底层, 它利用了JavaScript 事件循环——也就是运行定时器, AJAX 回调和所有浏览器事件的那个循环。只有当发生了变化时, 它才会重新渲染:
- 状态更新 (
useState,useReducer) - 父组件传递的 props 发生变化
- Context 值发生变化
- 你主动强制更新 (如
forceUpdate等)
所以, 如果没有人点击, 输入, 调整大小或触发定时器——React 会完全保持空闲。
是的, 记忆化依然是有开销的
有一种常见的想法是: “我把所有东西都用 useMemo() 包起来, React 就不会乱动了。”
差不多!但记忆化不是魔法。
const memoizedValue = useMemo(() => expensiveComputation(a, b), [a, b]);
确实, 如果依赖项没有变化, 这样可以避免重复计算函数。但 React 仍然需要检查:
a变了吗?b变了吗?
这是通过浅相等 (即 ===) 来判断的。它很快——但不是零成本。如果你不必要地对所有东西都做记忆化, 你其实是在为无收益的地方付出代价。
让我们来看看:
场景A: 记忆化有用
const sortedList = useMemo(() => list.sort(), [list]);
如果 list 很长且很少变化?✅ 值得记忆化。
场景B: 记忆化过度
const result = useMemo(() => 2 + 2, []);
更糟的是:
const styles = useMemo(() => ({ color: "red" }), []);
没必要。即使不记忆化也足够快。你只是用 CPU 检查了一下"什么都没变"。
那么什么时候应该用记忆化?
- 当计算非常耗时时
- 当该值会作为 prop 传递给
React.memo()的子组件时 - 当依赖项很少变化时
除此之外, 不用太担心。
说说 React 的空闲行为
有个很棒的事实: React 不会因为时间流逝就渲染。
React 的设计就是休眠, 直到有意义的事情发生。你可以这样理解:
while (true) {
if (somethingChanged) {
reactRender();
}
// 否则, 什么都不做
}
这里的"something"可以是用户输入, 定时器回调, 网络响应等。但它总是被外部事件触发。
所以, 如果你的应用处于空闲状态, 没有 state 或 props 变化——React 完全不会做任何事。这就是高效的设计。
内联样式 vs. useMemo 样式
内联样式 (每次渲染都会新建对象)
<button style={{ color: "red", backgroundColor: theme.bg }}>Click</button>
这样写没问题, 但每次渲染 React 都会看到一个新的对象。如果这个按钮包在 React.memo() 里, 会导致记忆化失效。
函数返回样式 (每次渲染依然新建对象)
const style = () => ({ color: "red" });
同样的问题——style() 每次都返回新对象。
✅ 最佳实践: useMemo 样式
const style = useMemo(() => ({ color: "red" }), []);
只有依赖项变化时才会重新计算——这样对象引用始终一致。
但再次强调: 只有在你把样式传递给子组件或渲染大量列表时才这样做。对于单个按钮?没必要记忆化。
浅比较: 轻量, 但不是免费的
每次 useMemo 或 React.memo 执行时, React 都会比较依赖数组或 props 的值。
const areSame = prev.a === next.a; // 浅比较
对于原始类型 (number, string 等) 来说, 这很快。但对于数组或对象, 只要引用变了, 就会被视为不同——即使内容相同。
示例
const list1 = [1, 2, 3];
const list2 = [1, 2, 3];
list1 === list2; // false
所以用数组或对象做记忆化时, 需要小心管理引用。
总结: React 的思维模型
想要真正掌握 React, 就要像 React 那样思考:
- 事件驱动, 而不是帧驱动
- 只有 state/props/context 变化时才更新
- 浅比较很快, 但不是零成本
- 记忆化是工具, 不是万能药
- 不要过早优化——在记忆化前先做性能测量
你的组件不需要"聪明", 只要它是可预测的。
剩下的, 交给 React 就好。
