React.memo
React 中基于 props 比较跳过子组件重复渲染的组件级优化方式。
[!info] related notes
- 所属 MOC: React MOC
- 上位主题: React组件渲染与重渲染
- 相关概念: react-use-memo, react-use-callback, React 中 props 身份与重渲染, ECMAScript原始值与引用值
React.memo
React.memo 是组件级优化工具。它的作用是:当 props 没变时,允许 React 跳过这个子组件的重复渲染。
一句话定义
如果一个子组件在相同 props 下总是产出相同结果,React.memo 可以帮助它减少不必要的重复执行。
它解决什么问题
- 父组件重渲染时,某些纯展示子组件其实没必要跟着反复执行
- 某些列表项或重组件渲染代价较高
它和 useMemo / useCallback 的关系
React.memo作用在组件层useMemo作用在值层useCallback作用在函数引用层
很多时候,React.memo 是否生效,又会受到传给子组件的对象、数组、函数引用是否稳定的影响。
为什么对象 props 经常让它失效
React.memo 的问题往往不在“组件写错了”,而在“父组件每次都造出了新引用”。
<Child config={{ size: 'small' }} />
即使内容看起来一样,这里的对象身份每次都不同,比较结果通常也会被当成“变了”。
什么时候值得用
- 子组件比较重
- 父组件重渲染频繁
- 组件确实经常收到相同 props
什么时候不值得用
- 组件本身很轻
- props 几乎每次都会变
- 为了“做优化”而让代码变复杂
最短记忆方式
React.memo 跳过的是“相同 props 下的重复组件执行”,不是所有渲染问题的万能药。
面试要点
来自 react-memo-use-memo-use-callback-interview-question 的面试视角整理。
一句话回答
只有当一个子组件本身渲染代价不低、父组件又经常重渲染、并且这个子组件经常收到相同 props 时,React.memo 才更可能带来实际收益。
最稳的回答主线
- 组件轻不轻
- props 稳不稳定
- 父组件重渲染频不频繁
- 为了生效,传入的对象和函数引用往往也要稳定
一个更完整的面试表达
可以这样答:
React.memo不是默认必加的优化。只有当一个子组件本身渲染代价不低、父组件又经常重渲染、并且这个子组件经常收到相同 props 时,它才更可能带来收益。
如果组件很轻,或者 props 每次都在变,那即使包了React.memo,实际效果也可能很有限。
一个典型例子
const ListItem = React.memo(function ListItem({ item, onSelect }) {
return <button onClick={() => onSelect(item.id)}>{item.name}</button>
})
如果这里的 onSelect 和 item 在多数情况下都稳定,React.memo 就更有可能帮你跳过无意义的重复执行。
为什么它经常“加了没效果”
因为很多人会同时传这种 props:
<Child config={{ size: 'small' }} onClick={() => doSomething(id)} />
对象和函数每次都会是新引用,React.memo 就很难真正生效。
一个更工程化的判断
是否值得用 React.memo,通常要同时满足:
- 子组件确实不轻
- 父组件确实常 rerender
- props 大部分时间稳定
- 经过 profiling 或实际观察确认有收益
最短记忆方式
React.memo 不是默认必加项,它要看子组件重不重、props 稳不稳、父组件刷不刷。
一句话回答
React.memo 缓存的是组件渲染结果边界,useMemo 缓存的是计算结果,useCallback 缓存的是函数引用。
最稳的回答主线
React.memo
- 用在组件层
- props 没变时可跳过组件重复渲染
useMemo
- 用在值层
- 适合缓存昂贵计算结果
useCallback
- 用在函数引用层
- 适合需要稳定函数引用的场景
一个更完整的面试表达
可以这样答:
三者都和“缓存”有关,但缓存的对象不同。
React.memo是组件级边界优化,尽量在 props 没变时跳过子组件重复执行;useMemo缓存的是某个计算结果;useCallback缓存的是函数引用。
所以它们虽然经常一起出现,但职责不是一回事。
一个最容易加分的补充
很多时候它们会配合使用:
- 父组件用
useMemo稳定对象 props - 父组件用
useCallback稳定函数 props - 子组件用
React.memo才更有机会真正跳过 rerender
一个典型例子
const handleClick = useCallback(() => {
doSomething(id)
}, [id])
const options = useMemo(() => ({ id }), [id])
return <Child onClick={handleClick} options={options} />
如果 Child 本身用了 React.memo,这条链路才更完整。
面试里最容易加分的一句
这三者都不是默认必加项,只有当它们真的减少了昂贵且无意义的工作时,才算有效优化。
常见误区
到处加 useCallback
如果函数引用变化没有产生实际问题,这样做只会增加复杂度。
把 useMemo 当成“任何值都要缓存”
只有昂贵计算才有明显收益。
最短记忆方式
memo 缓组件,useMemo 缓值,useCallback 缓函数。