React 中的 useCallback 和 useMemo 的区别,以及它们各自的使用场景

326 阅读2分钟
  1. 基本概念
  • useCallback: 缓存函数
  • useMemo: 缓存值
  1. 语法对比
// useCallback
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

// useMemo
const memoizedValue = useMemo(
  () => computeExpensiveValue(a, b),
  [a, b]
);
  1. 主要区别
  • useCallback 返回一个记忆化的函数
  • useMemo 返回一个记忆化的值
  • useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
  1. 使用场景

useCallback 适用场景:

// 父组件
function Parent() {
  const [count, setCount] = useState(0);
  
  // 不使用 useCallback
  const handleClick = () => {
    console.log('clicked');
  };
  
  // 使用 useCallback
  const handleClickMemoized = useCallback(() => {
    console.log('clicked');
  }, []); // 空依赖数组,函数永远不会改变

  return (
    <div>
      <Child onClickHandler={handleClickMemoized} />
      <button onClick={() => setCount(count + 1)}>Update Count</button>
    </div>
  );
}

// 子组件
const Child = React.memo(({ onClickHandler }) => {
  console.log("Child rendered");
  return <button onClick={onClickHandler}>Click me</button>;
});

useMemo 适用场景:

function ExpensiveComponent({ data }) {
  // 复杂计算的结果被缓存
  const processedData = useMemo(() => {
    return data.map(item => {
      // 假设这是一个复杂的计算
      return expensiveOperation(item);
    });
  }, [data]); // 只有当 data 改变时才重新计算

  return (
    <div>
      {processedData.map(item => (
        <div key={item.id}>{item.value}</div>
      ))}
    </div>
  );
}
  1. 性能优化建议
  • 不要过度使用这些 hooks,只在确实需要优化性能时使用
  • 合理设置依赖数组
  • 配合 React.memo 使用效果更好
  1. 常见陷阱
// ❌ 错误使用
function Component() {
  const [count, setCount] = useState(0);
  
  // 这种简单计算没必要使用 useMemo
  const doubleCount = useMemo(() => count * 2, [count]);
  
  // 这种简单函数没必要使用 useCallback
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);
}

// ✅ 正确使用
function Component({ onItemsChange, items }) {
  // 复杂计算使用 useMemo
  const sortedItems = useMemo(() => {
    return [...items].sort((a, b) => b.priority - a.priority);
  }, [items]);
  
  // 传递给子组件的回调函数使用 useCallback
  const handleChange = useCallback((newItems) => {
    const processed = someExpensiveOperation(newItems);
    onItemsChange(processed);
  }, [onItemsChange]);
}
  1. 性能影响
  • 过度使用这些 hooks 可能会导致内存使用增加
  • 在简单场景下使用反而会因为创建缓存而降低性能
  • 应该在以下情况使用:
    • 计算量大的操作(useMemo)
    • 需要保持引用相等的回调函数(useCallback)
    • 作为 props 传递给使用 React.memo 的子组件的函数

这个问题很好地体现了 React 性能优化的核心概念,也是实际开发中经常需要考虑的问题。理解这两个 hooks 的区别和适用场景,对于编写高性能的 React 应用至关重要。