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 次
