核心答案
React.memo管组件,决定组件要不要重新渲染;useMemo管值,决定一个计算要不要重新执行。
这就是核心,不要再往里面塞细节。
React.memo 到底做了什么?
React.memo 包裹的是组件:
jsx
const Child = React.memo(function Child({ name }) {
console.log('Child render');
return <div>{name}</div>;
});
当父组件重新渲染时,React 会拿 Child 的新旧 props 做比较。
text
父组件重新渲染
↓
Child 的 props
↓
新旧 props 浅比较
↓
没变化 → 可以跳过 Child
变化 → Child 重新渲染
所以 memo 的核心价值是:
父组件更新了,但子组件没必要跟着更新,就把子组件这次渲染跳过去。
memo 为什么用了还是会重新渲染?
最常见的原因就是 props 引用变了。
例如:
jsx
<Child user={{ name: 'Tom' }} />
父组件每次渲染都会创建一个新的对象:
js
{ name: 'Tom' } !== { name: 'Tom' }
虽然内容一样,但引用不同。数组、函数也是一样:
jsx
<Child
list={[1, 2, 3]}
onClick={() => console.log('click')}
/>
每次父组件重新执行:
text
新的数组引用
新的函数引用
↓
props 浅比较失败
↓
memo 无法跳过
所以如果确实需要稳定引用,可以:
jsx
const user = useMemo(() => ({
name: 'Tom'
}), []);
const onClick = useCallback(() => {
console.log('click');
}, []);
然后:
jsx
<Child user={user} onClick={onClick} />
这样才是:
text
useMemo / useCallback
↓
稳定 props 引用
↓
React.memo
↓
减少子组件重复渲染
memo 能不能阻止所有重新渲染?
不能。它主要解决的是:
父组件更新导致的、但 props 没变化的子组件重复渲染。
例如子组件自己的 state 更新:
jsx
const Child = React.memo(() => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
});
这里:
text
setCount()
↓
Child 自己发生更新
↓
Child 还是会重新渲染
所以不要把 memo 理解成"组件不渲染了"。
useMemo 又做什么?
useMemo 缓存的是一个值。
jsx
const result = useMemo(() => {
return hugeList
.filter(...)
.sort(...);
}, [hugeList]);
组件重新渲染时:
text
组件重新渲染
↓
检查依赖
↓
hugeList 没变
↓
直接拿之前的 result
↓
不用重新计算
所以它解决的是:
组件虽然重新渲染了,但里面有些昂贵计算没必要重复做。
这和 memo 是两个问题。
useMemo 能替代 memo 吗?
不能。
因为:
text
React.memo
↓
缓存组件的渲染结果
↓
减少组件渲染
useMemo
↓
缓存一个计算结果
↓
减少重复计算
所以:
jsx
const Child = React.memo(...);
和:
jsx
const result = useMemo(...);
根本不是一个层面的东西。
为什么 useMemo 经常和 memo 一起用?
例如:
jsx
const Child = React.memo(function Child({ config }) {
console.log('Child render');
return <div>{config.theme}</div>;
});
function Parent() {
const [count, setCount] = useState(0);
const config = useMemo(() => ({
theme: 'dark'
}), []);
return (
<>
<button onClick={() => setCount(count + 1)}>
{count}
</button>
<Child config={config} />
</>
);
}
这里实际上是两个优化:
text
useMemo
↓
config 引用保持稳定
↓
Child 的 props 没变化
↓
React.memo
↓
Child 可以跳过渲染
所以可以记成:
useMemo可以帮助稳定传给memo子组件的对象、数组引用,但它本身不负责阻止组件渲染。
useCallback 又是什么?
一句话:
useMemo缓存值,useCallback缓存函数。
例如:
jsx
const handleClick = useCallback(() => {
console.log('click');
}, []);
它最常见的用途就是:
text
父组件重新渲染
↓
不要每次创建新的函数引用
↓
Child props 引用保持稳定
↓
React.memo 才有机会跳过 Child
所以三者可以这样记:
text
React.memo
↓
管组件
↓
减少不必要的组件渲染
useMemo
↓
管值
↓
减少不必要的重复计算
useCallback
↓
管函数
↓
稳定函数引用
useMemo 为什么不能滥用?
因为 useMemo 也不是免费的。每次渲染仍然需要处理:
text
依赖比较
缓存管理
内存占用
如果只是:
jsx
const total = useMemo(() => a + b, [a, b]);
通常就没必要。因为 a + b 本身非常便宜,缓存它可能比直接计算还复杂。
真正适合的是:
text
大数组排序
复杂数据转换
大量过滤
复杂格式化
昂贵计算
或者:
需要稳定对象、数组引用,并且这个引用会影响
memo子组件是否重新渲染。
高频追问
Q:React.memo 默认比较什么?
新旧 props 的浅比较。
Q:对象 props 为什么容易让 memo 失效?
因为比较的是引用,不是对象内容。
Q:memo 能阻止 state 更新吗?
不能,组件自己的 state 更新仍然会触发更新。
Q:useMemo 能阻止组件重新渲染吗?
不能,它只是缓存计算结果。
Q:useMemo 和 useCallback 的区别?
useMemo缓存值,useCallback缓存函数。
Q:是不是所有地方都应该用 useMemo?
不是,计算很便宜或者没有实际性能问题时,没必要为了缓存而缓存。
Q:memo 一定能提升性能吗?
不一定。如果组件本身很轻,或者 props 经常变化,memo 的比较成本可能抵消收益。
最终背诵版
React.memo管组件,决定组件要不要重新渲染;useMemo管值,决定一个计算要不要重新执行。memo主要通过浅比较 props 来跳过不必要的组件更新,useMemo则是缓存计算结果,避免昂贵计算重复执行。两者可以配合,比如用useMemo稳定传给子组件的对象,再用memo减少子组件重复渲染。
最短记忆:
text
memo → 组件
useMemo → 值
useCallback → 函数