React Hook: useCallback 详解

什么是 useCallback

useCallback 是一个 React Hook,用于记忆化函数,它返回一个记忆化的回调函数版本,只有当其依赖项之一发生变化时才会改变。这有助于通过防止不必要的重新渲染和函数重新创建来优化性能。

基本语法

const memoizedCallback = useCallback(
  () => {
    // 函数体
  },
  [dependencies] // 依赖数组
);

为什么使用它?

  1. 防止不必要的重新渲染:当将函数作为 props 传递给记忆化的组件(React.memo)时,useCallback 确保函数引用保持不变,除非依赖项发生变化。
  2. 优化昂贵的操作:避免在每次渲染时重新创建函数。
  3. 稳定的引用:在其他 Hook(如 useEffectuseMemo 等)中作为依赖项时很有用。

实际示例:传递给子组件

这是一个 useCallback 真正重要的实际示例:

// 子组件(已记忆化)
const Button = React.memo(({ onClick, label }) => {
  console.log('Button rendered');
  return <button onClick={onClick}>{label}</button>;
});

// 父组件
function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  // ❌ 不使用 useCallback:每次 'name' 改变时 Button 都会重新渲染
  // const handleClick = () => setCount(count + 1);
  
  // ✅ 使用 useCallback:只有当 count 逻辑改变时 Button 才会重新渲染
  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);
  
  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <Button onClick={handleClick} label={`Count: ${count}`} />
    </div>
  );
}

何时使用 useCallback

在以下情况使用:

  • 将函数传递给记忆化的子组件(React.memo
  • 在其他 Hook(useEffectuseMemo)中作为依赖项使用的函数
  • 创建函数成本较高
  • 大型列表中的回调函数

何时不使用

在以下情况不要使用:

  • 不向子组件传递函数的组件中的简单事件处理程序
  • 无论如何都会频繁变化的函数
  • 过早优化——只有在存在性能问题时才使用

useMemo 的比较

  • useMemo:记忆化值/结果
  • useCallback:记忆化函数
// useMemo - 记忆化结果
const expensiveValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

// useCallback - 记忆化函数本身
const expensiveFunction = useCallback(() => computeExpensiveValue(a, b), [a, b]);

完整示例

这是一个展示 useCallback 实际应用的完整示例:

import { useState, useCallback, memo } from 'react';

// 记忆化的子组件
const ExpensiveChild = memo(({ onIncrement, count }) => {
  console.log('ExpensiveChild rendered');
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={onIncrement}>Increment</button>
    </div>
  );
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  // ✅ 记忆化的回调 - 除非依赖项改变,否则不会改变
  const handleIncrement = useCallback(() => {
    setCount(prev => prev + 1);
  }, []); // 空数组 = 函数永远不会改变
  
  return (
    <div>
      <input 
        value={text} 
        onChange={(e) => setText(e.target.value)} 
        placeholder="Type something..."
      />
      <ExpensiveChild onIncrement={handleIncrement} count={count} />
    </div>
  );
}

在这个示例中,在输入框中输入不会导致 ExpensiveChild 重新渲染,因为 handleIncrement 由于 useCallback 而具有稳定的引用。

关键要点

  1. useCallback 记忆化函数,而不是值
  2. 主要在将函数传递给记忆化组件时使用
  3. 优先使用函数式状态更新以最小化依赖项
  4. 不要过度使用——只有在有真正性能收益时才使用
  5. 始终在依赖数组中包含所有依赖项
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐