三个东西分别是什么
| 作用 | |
|---|---|
React.memo(Comp) |
包组件,props 没变则跳过 re-render |
useMemo(fn, deps) |
缓存计算结果(值) |
useCallback(fn, deps) |
缓存函数引用 |
后两个本质都是"deps 没变就返回上次的值",useCallback(f, d) 等价于 useMemo(() => f, d)。
默认你不需要它们
React 19 + Compiler 开启后,编译器自动加 memo,手动加反而画蛇添足。即使没用编译器:先确认有性能问题再说。
无脑包一层 memo 的代价:
- 代码可读性下降
- 每次 render 多一次比较开销
- 依赖数组没写对反而引入隐藏 bug
什么时候真的有用
场景 1:传给 memo 子组件的函数 prop
const Child = memo(({ onClick }) => { ... });
function Parent() {
const onClick = () => { ... }; // 每次 render 都是新函数
return <Child onClick={onClick} />; // ❌ Child 的 memo 失效
}
修法:
const onClick = useCallback(() => { ... }, []);
但如果 Child 没包 memo,useCallback 没意义——只是徒增开销。
场景 2:昂贵的计算
const sorted = useMemo(() => hugeList.sort(...), [hugeList]);
判断"昂贵"的方法:注掉 useMemo,看 React DevTools Profiler 时间。没过 1ms 不用 memo。
场景 3:稳定引用作为依赖
const config = useMemo(() => ({ x: 1 }), []);
useEffect(() => {
doSomething(config);
}, [config]); // config 引用稳定 → effect 不会反复跑
但其实可以更简单:把 config 放在 effect 里,或拆成原始值。
memo 怎么用
const Row = memo(function Row({ data }) {
return <div>{data.name}</div>;
});
默认浅比较 props。如果 props 含对象——上层必须保证引用稳定,否则 memo 等于没加。
自定义比较(很少用到):
const Row = memo(function Row(props) { ... }, (prev, next) => {
return prev.data.id === next.data.id;
});
useMemo 怎么用
const expensive = useMemo(() => {
return heavyCompute(items);
}, [items]);
只缓存结果,不能用来跳过副作用。
❌ 错误用法:
useMemo(() => fetch(...), []); // 不是 effect!
useCallback 怎么用
const handleSubmit = useCallback(() => {
api.submit(formId);
}, [formId]);
注意 formId 必须在依赖里,否则 stale closure。
性能优化的真正第一步
先 Profile,再优化:
- React DevTools → Profiler → 录制一次操作
- 看哪个组件 render 时间最长
- 看哪个组件 render 次数最多但没必要
- 针对那个组件再上 memo / useMemo / useCallback
闭眼到处加 memo 是反模式。
React Compiler(已稳定 · 默认推荐)
React Compiler 在 2024 年底进入 RC,2025 年随 React 19.x 走向稳定。新项目应该默认开启。
// next.config.js(Next.js 15+ 起 reactCompiler 已不在 experimental 下)
module.exports = { reactCompiler: true };
注:不同 Next.js 版本配置位置略有差异——以 官方文档当前页 为准。仍处于 experimental 的版本写
experimental: { reactCompiler: true }。
开启后,编译器静态分析你的代码自动加上 memo 优化。开发体验:继续写普通代码,不手动 memo。
Compiler 不是万能的——动态用法、外部库交互、违反 React Rules 的代码会被 "bailout"。需要看 ESLint eslint-plugin-react-compiler 的提示修代码,bailout 的部分手动加 memo 兜底。
→ 下一篇 useContext 与跨层数据