React的useCallback与useMemo的区别

450 阅读3分钟

在 React 中,useMemo 和 useCallback 虽然功能上有重叠(都可以缓存引用),但它们的设计意图语义化场景有明显区别。React 提供两者是为了让开发者更清晰地表达代码的意图,并减少误用风险。


1. 语义化区别:明确代码用途

  • useMemo
    用于缓存计算结果(可以是任何类型的值)。
    例如:const filteredList = useMemo(() => list.filter(item => item.active), [list])
    开发者意图:这里有一个计算成本较高的操作,需要缓存结果。
  • useCallback
    专门用于缓存函数引用
    例如:const handleClick = useCallback(() => console.log('Click'), [])
    开发者意图:这个函数需要保持引用稳定,避免子组件因函数引用变化而重新渲染。

分开设计的意义
开发者通过 Hook 名称就能明确代码的用途,而不是依赖技术细节(比如用 useMemo 包裹函数)。这提升了代码的可读性和维护性。


2. 减少误用风险

  • useMemo 缓存函数时需要手动包裹

    // 需要显式返回函数
    const handleClick = useMemo(() => () => {
      console.log('Click');
    }, []);
    

    如果开发者忘记包裹一层函数,直接返回函数本身:

    // ❌ 错误用法:直接返回函数,等同于每次返回新函数,失去缓存意义
    const handleClick = useMemo(() => {
      console.log('Click');
    }, []);
    

    这会导致逻辑错误,但 useCallback 直接避免此问题:

    // ✅ 正确且简洁
    const handleClick = useCallback(() => console.log('Click'), []);
    
  • useCallback 强制约束返回值类型
    useCallback 保证返回值始终是一个函数,而 useMemo 可以返回任何类型。如果误用 useMemo 缓存函数时返回了非函数值,会导致运行时错误:

    // ❌ 错误示例:返回字符串而非函数
    const brokenHandler = useMemo(() => "not a function", []);
    

3. 性能优化的场景不同

  • useMemo 的核心价值是避免重复计算
    适用于需要复杂计算的场景(如数据转换、大型列表过滤等):

    const heavyResult = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    

    如果不用 useMemo,每次渲染都会重新计算 computeExpensiveValue,可能造成性能问题。

  • useCallback 的核心价值是保持引用稳定
    函数本身的创建成本极低,但引用变化会导致子组件不必要的重渲染:

    // 父组件
    const Parent = () => {
      const handleChildEvent = useCallback(() => { /* ... */ }, []);
      return <Child onClick={handleChildEvent} />;
    };
    
    // 子组件用 React.memo 避免无效渲染
    const Child = React.memo(({ onClick }) => { /* ... */ });
    

    如果不用 useCallback,父组件每次渲染都会生成新的 handleChildEvent,导致 Child 组件重新渲染。


4. 底层实现的一致性

  • 从源码看,useCallback 实际上是 useMemo 的特化版本:

    function useCallback(fn, deps) {
      return useMemo(() => fn, deps);
    }
    
  • React 团队选择分开它们,是为了提供更直观的语义化 API,而非技术必要性。


总结:为什么需要同时存在?

维度useMemouseCallback
用途缓存计算结果(任何类型)缓存函数引用
设计意图避免重复计算保持函数引用稳定
代码可读性明确表达“缓存计算结果”明确表达“缓存函数”
防止误用可能返回非函数值强制返回函数

React 通过提供两个专用 Hook,让开发者:

  1. 更清晰地表达代码意图(缓存函数 vs 缓存值),
  2. 减少因误用 useMemo 导致的潜在错误,
  3. 在特定场景下写出更简洁、高效的代码。