React 中的 useCallback 详解

kkcode
kkcode
2026-02-13阅读 761

useCallback 是 React 提供的性能优化 Hook,核心作用是缓存函数,避免组件重新渲染时函数被重复创建,主要用于解决子组件不必要的重新渲染问题,是 React 性能优化的常用手段之一。

一、核心作用

  • 缓存函数实例:组件重新渲染时,返回缓存的函数引用,而非创建新函数。
  • 减少子组件重渲染:当父组件传递函数给子组件时,若函数引用不变,子组件可跳过不必要的重渲染(需配合 memo 使用)。
  • 优化依赖项:在 useEffectuseMemo 等依赖数组中使用,避免因函数引用变化导致依赖项频繁触发。

二、基本用法

1. 引入与语法

react 中导入 useCallback,在函数组件顶层调用,语法如下:

import { useCallback } from 'react';

function 组件名() {
  // 语法:const 缓存函数 = useCallback(原函数, 依赖数组);
  const cachedFn = useCallback(() => {
    // 函数逻辑(可访问组件的 props、state 等)
  }, [依赖项1, 依赖项2]); // 依赖数组:与 useEffect 一致
  
  return UI结构;
}

2. 关键参数说明

  • 原函数:需要缓存的函数,逻辑与普通函数一致。
  • 依赖数组:控制缓存的核心,与 useEffect 规则完全相同:
    1. 不传依赖数组:组件每次渲染都会返回新函数(等于没缓存,不推荐)。
    2. 空数组 []:仅首次渲染创建函数,后续永远返回缓存的函数引用。
    3. 包含依赖项:仅当依赖项变化时,才会重新创建并缓存新函数,否则返回旧引用。
  • 返回值:缓存后的函数实例,依赖不变时,多次渲染返回的函数引用完全相同。

三、实用场景与示例

场景 1:配合 memo 避免子组件重渲染(最常用)

父组件传递函数给子组件时,默认每次父组件渲染都会创建新函数,导致子组件即使 props 未变也会重渲染。用 useCallback 缓存函数,配合 memo 包裹子组件,可解决此问题。

示例代码:

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

// 子组件:用 memo 包裹,仅当 props 变化时才重渲染
const Child = memo(({ onClick }) => {
  console.log('子组件重渲染'); // 仅用于测试是否重渲染
  return <button onClick={onClick}>点击我</button>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  // 用 useCallback 缓存函数,依赖数组为空(函数逻辑不依赖任何状态)
  const handleClick = useCallback(() => {
    alert('子组件按钮被点击');
  }, []); // 空依赖:函数引用永远不变

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>增加计数</button>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="输入文本"
      />
      {/* 传递缓存后的函数给子组件 */}
      <Child onClick={handleClick} />
    </div>
  );
}

效果说明:

  • 点击“增加计数”或输入文本时,父组件重渲染,但 handleClick 引用不变。
  • 子组件因 memo 检测到 onClick 引用未变,不会重渲染(控制台无“子组件重渲染”日志)。

场景 2:优化 useEffect 依赖项

useEffect 依赖一个函数时,若函数每次渲染都变化,会导致 useEffect 频繁执行。用 useCallback 缓存函数,可稳定依赖项。

示例代码:

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

function DataFetcher() {
  const [userId, setUserId] = useState(1);
  const [data, setData] = useState(null);

  // 缓存请求函数,依赖 userId
  const fetchData = useCallback(async () => {
    const res = await fetch(`https://api.example.com/user/${userId}`);
    const userData = await res.json();
    setData(userData);
  }, [userId]); // 仅 userId 变化时,重新创建 fetchData

  // useEffect 依赖 fetchData,因 fetchData 被缓存,仅 userId 变化时执行
  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return (
    <div>
      <button onClick={() => setUserId(userId + 1)}>切换用户 ID:{userId}</button>
      {data && <p>用户名:{data.name}</p>}
    </div>
  );
}

场景 3:缓存复杂计算函数

若函数内部有复杂计算逻辑,且不频繁变化,用 useCallback 缓存可避免重复计算(配合 useMemo 效果更佳)。

四、核心特性

  1. 缓存函数引用:本质是缓存函数的内存地址,而非函数逻辑,依赖不变时引用永远相同。
  2. 必须配合 memo/useMemo 生效:单独使用 useCallback 无性能优化效果,需子组件用 memo 或其他缓存机制配合。
  3. 依赖规则严格:与 useEffect 一致,函数内使用的外部变量必须加入依赖数组,否则会导致逻辑错误。
  4. 仅用于性能优化:非必要场景(如简单组件、无频繁重渲染)无需使用,过度使用会增加代码复杂度。

五、常见误区

  1. 单独使用 useCallback:以为缓存函数就能优化性能,却未用 memo 包裹子组件,子组件仍会重渲染,优化失效。
  2. 遗漏依赖项:函数内使用了状态/属性,却未加入依赖数组,导致函数内拿到的是旧值,逻辑错误。
  3. 过度使用:所有函数都用 useCallback 缓存,反而增加代码维护成本,且 useCallback 本身有微小性能开销,简单函数无需缓存。
  4. 依赖数组传空却使用外部变量:空依赖数组表示函数不依赖任何变量,若函数内使用了外部状态,需加入依赖,否则会拿到旧值。

六、与 useMemo 的区别

很多人会混淆 useCallbackuseMemo,核心区别如下:

  • useCallback:缓存函数本身,返回函数引用。
  • useMemo:缓存函数的执行结果,返回计算后的值。

示例对比:

// useCallback:缓存函数
const fn = useCallback(() => count * 2, [count]);

// useMemo:缓存函数结果
const value = useMemo(() => count * 2, [count]);

总结

  1. useCallback缓存函数的性能优化 Hook,核心是稳定函数引用,避免子组件不必要重渲染。
  2. 必须配合 memo 包裹子组件才能生效,依赖数组规则与 useEffect 完全一致。
  3. 仅在需要优化频繁重渲染的场景使用,避免过度优化,简单场景直接用普通函数即可。
评论数量:0