在 React 性能优化中,React.memo 是一个非常常见但又经常被误解的 API。
许多开发者知道它“能防止子组件重复渲染”,但具体跳过的是“哪个阶段”?
它到底是“跳过更新”还是“跳过渲染”?
React.memo 是什么
React.memo 是一个 高阶组件(Higher Order Component),用于缓存(记忆)函数组件的渲染结果
当传入的 props 没有发生变化时,React 会直接复用上一次的渲染结果
import React from "react";
const Child = React.memo(function Child({ name }) {
console.log("Child render");
return <div>{name}</div>;
});
当父组件更新时,只要 name 没有变化,Child 组件就不会重新渲染。
React 渲染的三个阶段
要理解 React.memo,先要搞清楚 React 渲染的大致流程。
每次组件更新,React 都会经历这三个阶段:
| 阶段 | 作用 |
|---|---|
| Render 阶段 | 执行组件函数,生成新的 Virtual DOM |
| Reconciliation 阶段 | 比较新旧 Virtual DOM 的差异 |
| Commit 阶段 | 把变化更新到真实 DOM 上 |
普通子组件的渲染过程
当父组件状态变化时,普通子组件会始终执行函数生成新的虚拟 DOM:
function Child({ name }) {
console.log("Child render");
return <div>{name}</div>;
}
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
<Child name="Jack" />
</>
);
}
每点击一次按钮,控制台输出:
Child render
说明:
- 父组件更新 → 子组件也进入了 Render 阶段
- React 比较虚拟 DOM,发现没变 → 不更新真实 DOM(跳过 commit 阶段)
也就是说,普通组件即使内容没变,render 也会执行一次。
React.memo 的优化:直接跳过渲染阶段
再看同样的例子,加入 React.memo:
const Child = React.memo(({ name }) => {
console.log("Child render");
return <div>{name}</div>;
});
这时点击按钮,只会打印:
(父组件 render)
没有 “Child render” 了。
为什么?
因为 React 在父组件更新时,先比较:
Object.is(prevProps.name, nextProps.name);
结果相同,React 直接复用上一次渲染结果。
这意味着:
React.memo 不只是跳过更新,它连「渲染阶段」都不会执行。
阶段对比表
| 阶段 | 普通子组件 | React.memo 子组件(props 未变) |
|---|---|---|
| Render 阶段 | ✅ 会执行函数生成 Virtual DOM | ❌ 直接跳过 |
| Reconciliation 阶段 | ✅ 比较新旧虚拟 DOM | ❌ 复用上次结果 |
| Commit 阶段 | ❌ 通常不更新(若内容一致) | ❌ 不触发任何更新 |
总结
🔹 普通组件:父组件更新 → 仍然执行渲染,只是可能不更新 DOM。
🔹 React.memo 组件:props 没变 → 连渲染函数都不会执行。
引用类型的陷阱
React.memo 使用的是浅层比较(shallow equal)
如果 props 是对象、数组或函数,即使内容相同,每次 render 都会创建新引用:
<Child user={{ name: "Jack" }} /> // 每次都是新对象
这会导致 memo 失效,因为:
prevProps.user !== nextProps.user;
✅ 解决办法:
const user = useMemo(() => ({ name: "Jack" }), []);
<Child user={user} />;
React.memo 的适用场景
| 场景 | 是否推荐使用 |
|---|---|
| 子组件渲染代价大 | ✅ 推荐 |
| props 通常不变 | ✅ 推荐 |
| props 频繁变化 | ❌ 无意义 |
| props 是复杂引用类型(对象、函数) | ⚠️ 小心浅比较 |
| 子组件依赖 Context 或自身状态 | 🚫 无效或有限效果 |
自定义比较函数
可以传入第二个参数,自定义 props 比较逻辑:
const Child = React.memo(
({ user }) => <div>{user.name}</div>,
(prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);
当返回 true 时,React 认为 props 相同 → 跳过渲染。
当返回 false 时 → 重新渲染。
概括总结
| 对比项 | 普通组件 | React.memo 组件 |
|---|---|---|
| props 比较 | 无 | 浅比较(Object.is) |
| render 阶段 | 每次执行 | props 不变则跳过 |
| commit 阶段 | 可能执行 | 完全跳过 |
| 性能 | 简单组件性能好 | 复杂组件提升明显 |
| 类似概念 | - | PureComponent(类组件) |
React.memo 会在 props 没变时,直接跳过整个渲染阶段,复用上一次的渲染结果,是函数组件的轻量级性能优化手段。