三个东西分别是什么

作用
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,再优化

  1. React DevTools → Profiler → 录制一次操作
  2. 看哪个组件 render 时间最长
  3. 看哪个组件 render 次数最多但没必要
  4. 针对那个组件再上 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 与跨层数据