React 中 React.memo 、useCallback、useMemo 如何正确使用解读

作者: tww844475003 分类: 前端开发 发布时间: 2025-04-02 00:06

React.memo 作用

React.memo是一个高阶组件,用于函数组件,通过浅比较props的变化来决定是否重新渲染。如果props没有变化,组件就会跳过渲染,复用之前的渲染结果,从而提升性能。

React.memo 使用原则

场景 1:组件渲染成本高

如果组件内部有复杂计算、大量子节点或高频交互(如动画、图表),且父组件频繁触发无关渲染时,使用 React.memo 可减少重复渲染。

场景 2:Props 变化频率低

当组件的 ‌props 大多数情况下稳定‌,只有少数情况会变化时(如配置型组件),React.memo 能有效避免因父组件状态变化导致的无效渲染。

场景 3:传递了非稳定 Props

如果父组件传递的 props 是内联对象或函数(如 onClick={() => {...}}),且未通过 useMemo/useCallback 缓存,子组件用 React.memo 可能无效,需结合缓存使用。

场景 1:组件本身渲染成本极低

如果组件只是简单渲染文本或少量 DOM 节点(如 <Button>),React.memo 的 props 浅比较成本可能高于直接渲染的成本,得不偿失。

场景 2:Props 频繁变化

如果组件的 props ‌每次渲染几乎都会变化‌(如实时更新的数据流),使用 React.memo 反场景 1:子组件使用了 React.memo‌而会增加额外的浅比较开销,优化效果微乎其微。

场景 3:组件已经是“叶子组件”

如果组件没有子组件,且不受父组件状态影响,通常不需要额外优化。

如果不想滥用 React.memo,可通过其他方式减少渲染:

  1. 状态隔离‌:将状态下沉到更小的组件中,避免全局状态触发大范围渲染。
  2. 组件拆分‌:将高频变动的部分和低频变动的部分拆分为独立组件。
  3. 使用 key 属性‌:强制重置组件实例,避免内部状态混乱(如列表项)。
  4. 虚拟化长列表‌:对长列表使用 react-window 或 react-virtualized,减少 DOM 节点数量。

useCallback作用

useCallback 是 React 中用于性能优化的钩子,其主要作用是 ‌缓存函数实例‌,避免子组件因父组件重新渲染导致的非必要更新。

useCallback 使用原则

场景 1:子组件使用了 React.memo

如果子组件通过 React.memo 避免重复渲染,且父组件传递的方法是一个 ‌非稳定的函数引用‌(如内联函数),则需要用 useCallback 包裹,否则子组件会因父组件渲染导致函数引用变化而重新渲染。

// 父组件
const Parent = () => {
  const [count, setCount] = useState(0);

  // 未使用 useCallback:每次渲染生成新函数,导致子组件重新渲染
  const handleClickBad = () => console.log("Click");

  // 使用 useCallback:函数引用稳定,子组件不重复渲染
  const handleClickGood = useCallback(() => {
    console.log("Click");
  }, []);

  return <Child onClick={handleClickGood} />;
};

// 子组件
const Child = React.memo(({ onClick }) => {
  return <button onClick={onClick}>Submit</button>;
});

场景 2:子组件依赖浅比较优化

如果子组件是 PureComponent 或通过 shouldComponentUpdate 实现了浅比较逻辑,父组件传递的函数必须保持引用稳定,否则优化会失效。

class Child extends React.PureComponent {
  render() {
    return <button onClick={this.props.onClick}>Submit</button>;
  }
}

场景 1:子组件无渲染优化

如果子组件没有使用 React.memoPureComponent 或自定义的渲染优化逻辑,即使父组件传递的函数引用变化,也不会带来明显的性能问题。

// 子组件未优化,函数引用变化不影响性能
const Child = ({ onClick }) => <button onClick={onClick}>Submit</button>;

场景 2:函数依赖频繁变化的值

如果函数内部依赖频繁变化的 state 或 props,且需要‌实时获取最新值‌,此时 useCallback 需明确声明依赖项,可能导致函数频繁重建,反而失去优化意义。

const Parent = () => {
  const [text, setText] = useState("");

  // 依赖 text 变化,useCallback 无法避免重建
  const handleSubmit = useCallback(() => {
    console.log(text); // 需要最新的 text
  }, [text]);

  return <Child onSubmit={handleSubmit} />;
};

3. 替代方案:直接传递内联函数

如果子组件渲染成本极低(如简单按钮),且父组件渲染频率不高,可以直接传递内联函数,避免过度优化。

const Parent = () => {
  return <Child onClick={() => console.log("Click")} />;
};
场景是否需要 useCallback原因
子组件通过 React.memo/PureComponent 优化✅ 需要避免函数引用变化导致子组件无效渲染
子组件无优化且渲染成本低❌ 不需要优化收益小于比较成本
函数依赖高频变化的值❌ 谨慎使用可能导致频繁重建函数
函数作为副作用依赖(如 useEffect✅ 需要避免副作用重复触发

总结

  • 优先使用 useCallback‌:当子组件有明确的渲染优化策略时(如 React.memo)。
  • 无需强制使用‌:如果子组件渲染成本低或函数依赖频繁变化的值。
  • 避免滥用‌:过度使用 useCallback 可能导致代码复杂度上升,需结合性能分析工具(如 React DevTools)验证优化效果。

useMemo作用

useMemo 是 React 中的一个性能优化 Hook,‌核心作用是通过缓存复杂计算结果,避免组件重复渲染时不必要的重复计算‌。

场景 1:高开销计算

当组件内有‌计算成本高昂‌的操作(如大数据处理、复杂数学运算),且计算结果在多次渲染间可复用时。

场景 2:引用稳定性

当需要保持对象或数组的‌引用稳定‌,避免子组件因浅比较重新渲染时。

场景 3:依赖其他 Hook 的中间值

当某个值被多个 Hook 依赖,且需要避免重复计算时

场景 1:简单计算

如果计算成本极低(如基本运算、简单对象合并),直接计算即可。

场景 2:频繁变化的依赖项

如果依赖项频繁变化(如实时输入框的值),缓存效果微乎其微,反而增加开销。

场景 3:组件层级低或渲染压力小

对于叶子组件或渲染压力较小的组件,优化收益低于 useMemo 自身成本。

前端开发那点事
微信公众号搜索“前端开发那点事”

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表回复