React.memo与useCallback工作原理及memo优化适用场景
React.memo 作用与渲染机制误区解答
核心认知误区
你对React渲染逻辑的理解存在两个关键偏差:
- 默认渲染规则认知错误:React从来没有「props不变子组件就不重渲染」的默认逻辑,真实规则是:只要父组件触发重渲染,所有嵌套的子组件无论props是否变化,都会默认执行完整的组件函数调用、新旧虚拟DOM对比流程。props变化是父组件重渲染带来的伴随结果,不是子组件重渲染的前置触发条件。
- 示例统计逻辑错误:你在示例里用子组件内部
useEffect监听fn变化累加的state值,根本不能代表组件实际渲染次数——这个effect只有在fn的引用地址变化时才会触发,和组件渲染多少次没有直接对应关系。真正能反映渲染次数的是你写在子组件函数顶层的console.log打印,打开控制台点几次父组件的状态更新按钮就能看到真实结果:- 没被
memo包裹的两个普通子组件,不管传入的fn是不是用useCallback缓存过,每次父组件重渲染都会打印渲染日志,也就是每次都会跟着重渲染 - 被
memo包裹的子组件,只有props浅比较不通过时才会重渲染:传入每次新建的普通函数的memo子组件,每次父组件渲染都会重渲染(因为函数每次都是新引用,浅比较判定props变化);传入useCallback缓存函数的memo+useCb子组件,仅首次渲染会打印日志,后续父组件更新状态时完全不会重渲染。
- 没被
useCallback 与 React.memo 的协作关系
这两个API定位完全不同,不存在谁替代谁的说法:
useCallback本身没有任何阻止组件重渲染的能力,它的唯一作用是在依赖项不变时,缓存函数的内存引用地址,保证多次渲染拿到的是同一个函数实例。React.memo才是真正负责跳过组件重渲染的API:它会在父组件重渲染时,对当前组件接收的所有props做浅比较,如果所有props的值/引用都和上一次渲染完全一致,就直接复用上一次的渲染结果,跳过本次组件执行和虚拟DOM对比流程。
两者通常是配合使用的:如果给memo包裹的组件传函数类型的prop,不搭配useCallback缓存函数引用的话,每次父组件渲染生成的新函数都会让memo的浅比较失效,memo的优化作用完全发挥不出来;反过来只使用useCallback缓存函数,不给子组件加memo做props比较,父组件重渲染时子组件依然会无条件重渲染,useCallback本身挡不住渲染流程。
额外提一句:你在示例里直接在JSX属性位置调用useCallback的写法不符合React Hook使用规范,所有Hook必须提到组件函数的顶层作用域调用,避免出现调用顺序错乱导致的诡异bug。
React.memo 的适用场景
React.memo不是所有组件都要加的通用优化,仅在以下场景能拿到明确的性能收益:
- 组件本身渲染开销很高:比如包含复杂数据计算、长列表渲染、大型图表绘制、多表单项联动等逻辑,单次渲染的计算成本远高于memo做props浅比较的成本
- 组件的props可以稳定保持不变:传给组件的基础类型值、函数、复杂对象都能通过
useMemo/useCallback/状态提升等方式保持引用稳定,memo的浅比较大概率能命中缓存 - 组件作为通用基础组件被高频复用:比如按钮、表单项、卡片等跨页面通用组件,上层父组件的重渲染逻辑不可控,加memo可以避免无意义的重复渲染
如果组件本身逻辑非常轻量,或者组件接收的props每次渲染都会变化,就不要加memo——浅比较本身有计算开销,这种场景下加memo反而会降低运行性能。
内容的提问来源于stack exchange,提问作者Nemus
相关产品推荐
相关产品推荐

