React 性能优化:memo、useMemo 与 useCallback 的正确边界

test12026-09-140 次阅读

为什么组件会无谓重渲染

React 的渲染是自上而下的。当某个组件调用 setState,它本身及其所有子孙默认都会重新渲染,除非你显式地拦一道。性能问题的本质,往往不是“渲染太慢”,而是“渲染了本不必渲染的东西”。

React.memo 不是银弹

React.memo 通过对 props 做浅比较来决定是否跳过子组件渲染。但它只在“props 引用没变”时才生效。下面这个写法里,父组件每次渲染都新建一个数组传给 List,memo 永远拦不住:

function Parent() {
  const [count, setCount] = useState(0);
  // 每次渲染都生成一个新数组,引用永远不同
  const items = [{ id: 1, name: "A" }, { id: 2, name: "B" }];
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>count: {count}</button>
      <List items={items} />
    </div>
  );
}

const List = React.memo(function List({ items }) {
  return (
    <ul>
      {items.map((i) => (
        <li key={i.id}>{i.name}</li>
      ))}
    </ul>
  );
});

修复方式是用 useMemo 把 items 缓存起来,让它的引用在依赖不变时保持稳定:

const items = useMemo(
  () => [{ id: 1, name: "A" }, { id: 2, name: "B" }],
  []
);

useMemo:缓存计算结果,而不是“防止重渲染”

useMemo 真正的价值是避免昂贵的重复计算,比如对大列表做过滤、排序、聚合。它不会显著降低渲染次数,只是让每次渲染更便宜。不要为了“包住整个组件”而滥用它,那反而增加了闭包与依赖追踪的成本。

const filtered = useMemo(
  () =>
    data
      .filter((d) => d.score > threshold)
      .sort((a, b) => b.score - a.score),
  [data, threshold]
);

useCallback:给 memo 子组件稳定的函数引用

只有当一个函数要作为 prop 传给 React.memo 子组件时,useCallback 才真正有意义——它能保证函数引用稳定,避免子组件因“收到新函数”而被迫重渲染。

const handleSelect = useCallback(
  (id) => setSelectedId(id),
  [setSelectedId] // setState 本身稳定,依赖可只写它
);

// 传给被 memo 的子组件
<RowList onSelect={handleSelect} />

经验清单

  • 先测量再优化:用 React DevTools 的 Profiler 找到真正重复渲染的组件,不要凭感觉加 memo。
  • React.memo 只对“引用型 props 稳定”的子组件有效,原始值 props 无所谓。
  • useMemo / useCallback 本身有成本,只用于昂贵计算或需要稳定引用的场景。
  • 状态尽量下放到真正需要的叶子组件,比在顶层 memo 一堆子组件更有效。
T

test1

文章作者

为什么组件会无谓重渲染 React 的渲染是自上而下的。当某个组件调用 setState,它本身及其所有子孙默认都会重...

分类
技术
发布时间
2026-09-14
字数
约 1681 字
阅读
0 次