当前位置: 首页 > 知识库问答 >
问题:

javascript - 关于 react 中 useCallback 的使用问题?

邹星火
2025-03-13

一般情况下当函数作为依赖项时,应该使用 useCallback 来保证引用的稳定。例如组件的 props 或者是 useEffect 的依赖项。那假如函数被封装到一个 hook 中,那是都包一个 useCallback 好还是都不包,让使用者自已决定是否包?

共有4个答案

程谭三
2025-03-13

最简单的做法:

  1. 如果函数的声明本身没什么开销,比如你定义一个没有依赖的函数,根据传入的参数计算得到结果。那么你就不需要用 useCallback,每次都声明就好,函数声明的开销其实很低。
  2. 如果函数里要用当前 hook 的状态,但都是不变的,或者 useRef,那么也不需要。
  3. 只有当你的 hook 会被多次调用(即调用 hook 的组件会被有状态更新),导致内部依赖不稳定时,才需要用到 useCallback
  4. 这个原则,可以用 JS 闭包来推导出来。即每次组件状态变化,都会导致渲染函数被执行,继而导致内部状态被新变量重新引用,于是跟变量相关的函数都需要被重新声明。
  5. 我认为理解最后这点最为重要,原理很简单,但理解了原理,才能作出推论。
濮阳默
2025-03-13

在hook 内用 useCallback

import { useState, useCallback } from 'react';

function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  
  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);
  
  const decrement = useCallback(() => {
    setCount(prev => prev - 1);
  }, []);
  
  const reset = useCallback(() => {
    setCount(initialValue);
  }, [initialValue]);
  
  return {
    count,
    increment,
    decrement,
    reset
  };
}

页面里:

function CounterComponent() {
  const { count, increment, decrement, reset } = useCounter(0);
   
  useEffect(() => 
    console.log('Counter setup');
    
    return () => {
      // 清理逻辑
    };
  }, [increment]); // 依赖项
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
      <ChildComponent onIncrement={increment} />
    </div>
  );
}

不在 hook 内部用 useCallback

import { useState } from 'react';

function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  
  // 不使用 useCallback
  const increment = () => {
    setCount(prev => prev + 1);
  };
  
  const decrement = () => {
    setCount(prev => prev - 1);
  };
  
  const reset = () => {
    setCount(initialValue);
  };
  
  return {
    count,
    increment,
    decrement,
    reset
  };
}

页面里:

function CounterComponent() {
  const { count, increment, decrement, reset } = useCounter(0);
  
  const stableIncrement = useCallback(increment, [increment]);
  const stableDecrement = useCallback(decrement, [decrement]);
  const stableReset = useCallback(reset, [reset]);
  
  useEffect(() => {
    console.log('Counter setup');
    //引用
  }, [stableIncrement]);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
      
      <ChildComponent onIncrement={stableIncrement} />
    </div>
  );
}
薛霄
2025-03-13

建议采用两个版本的函数。

function useCustomHook() {
  // ...其他逻辑
  
  // 未记忆化版本
  const handleSomething = () => {
    // 函数逻辑
  };
  
  // 记忆化版本
  const handleSomethingCallback = useCallback(handleSomething, [/* 相关依赖 */]);
  
  return { 
    handleSomething,          // 普通版本
    handleSomethingCallback   // 记忆化版本
  };
}

或者通过选项参数提供灵活性:

function useCustomHook({ memoized = false } = {}) {
  // ...其他逻辑
  
  const handleSomething = () => {
    // 函数逻辑
  };
  
  return { 
    handleSomething: memoized 
      ? useCallback(handleSomething, [/* 相关依赖 */]) 
      : handleSomething
  };
}

这样可以在提供便利的同时保持灵活性,让使用者能根据自己的需求选择是否使用记忆化版本的函数。

如果你的 hook 返回的函数很可能被用作依赖项,那么默认使用 useCallback 是更友好的选择。
韩涵衍
2025-03-13

在自定义 Hook 中主动使用 useCallback 是更好的实践,但需要遵循特定规则:

  1. 封装原则:Hook 应该自包含地管理自己的依赖关系,返回稳定引用

    // 正确示例:在自定义 Hook 内部处理稳定性
    function useCustomHook() {
      const stableFn = useCallback(() => {
     // 逻辑代码
      }, [/* 正确依赖 */]);
      
      return stableFn;
    }
  2. 需要暴露依赖的场景
  3. 当 Hook 函数依赖外部参数时,应该将参数加入依赖数组
  4. 如果 Hook 内部操作依赖外部状态,需要明确要求使用者传递稳定值
  5. 例外情况
  6. 函数需要故意保持最新闭包时(如实时事件处理)
  7. 函数内部依赖频繁变化且无法通过依赖数组优化时
  8. 使用者注意事项

    // 错误用法:直接传递内联函数
    const unstableValue = { id: 42 };
    useCustomHook(unstableValue); // 会导致依赖数组失效
    
    // 正确用法:使用者主动稳定依赖
    const stableValue = useMemo(() => ({ id: 42 }), []);
    useCustomHook(stableValue);

最佳实践路径:

  1. 在自定义 Hook 内部对所有返回函数使用 useCallback
  2. 明确声明所有依赖项(使用 eslint-plugin-react-hooks 验证)
  3. 在文档中说明 Hook 的稳定性保证和依赖要求
  4. 如果 Hook 的参数可能是动态值,在文档中提示使用 useMemo/useCallback 的必要性

这种设计既保证了 Hook 的封装性,又通过明确的依赖声明让使用者能够正确配合使用。

 类似资料:
  • React中的useCallback 下面的代码中Com是父组件,Button是子组件,子组件接收父组件的count2和setCount2,子组件中使用了memo(Button)导出 子组件Button 在上面的代码中,如果点击count1++的按钮(不使用useCallback),父组件会重新渲染,但是子组件也会刷新,然而子组件中的count2依赖没有变化,所以只能是handleClick2函数

  • react 如何使用useCallback 避免全量更新? 在子组件的事件中使用useCallback 父组件 list.map 创建了子组件, 子组件触发事件导致 父组件的 list 发生变更, 于是组件全部发生了变化 这个过程中只有其中一个子组件发生了改变,所以我使用了 memo 包裹了一下 但是 传递的事件函数会重复创建,导致子组件全量更新 我应该如何使用useCallback 来实现顶点更

  • 我用亚马逊云的Lambda函数,通过langChain调用OpenAI API。我把OPENAI_API_KEY存在了Lambda函数的环境变量里了。神奇的是,在创建new ChatOpenAI()实例的时候没有传入OPENAI_API_KEY,还是可以获取到正确的返回值。这是为什么?

  • 看看这两个输出的为什么不一样?

  • 以下代码在 chrome 输出 1,2,3 这个在网上找到了,forEach 一开始就已经获取了 数组长度 The range of elements processed by forEach is set before the first call to callbackfn. Elements which are appended to the array after the call to

  • 关于JS的Promise同步调用问题 请问函数 xxx 中没有调用 resolve,也没有调用 reject,那么 yyy 函数运行到 await xxx () 是结束运行还是在一直阻塞? 并没有执行console.log('yyy调用xxx结束', res),请问程序是自动结束了还是阻塞中?

  • React,使用react-transition-group插件,想实现两个组件切换的一个转场效果,发现跟原网站的效果不符合,想知道问题出在哪里 原网站的转场效果是这样: 点击按钮,页面从右向左滑动,而且两个页面是紧贴着的(想要实现紧贴着的转场) 我实现的却是这样,两个页面没有紧贴,而且还多出一大片空白 附上代码 css 请大佬纠正!

  • 在放开最后一行注释后执行 : A的引用内存反而降低了,A对B的引用好像并没有解除,为什么会降低内存了? 为什么A对象的内存降低了呢