You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中useCallback在ref回调与点击事件的表现差异原因探究

React中useCallback包裹ref回调与点击事件的执行差异解析

问题代码与现象

function UseCallbackDemo() {
  const divRef = React.useRef<HTMLDivElement | null>(null);
  const [count, setCount] = React.useState(0);

  console.log("**** re-render component");
  const divRefCallback = React.useCallback((div) => {
    if (div) {
      divRef.current = div;
    }
    console.log("*** divRefCallback");
  }, []);

  const handleOnClick = React.useCallback(() => {
    console.log('**** handleOnClick');
    setCount(count => count + 1);
  }, []);

  return (
    <>
      <div ref={divRefCallback}>{count}</div>
      <button onClick={handleOnClick}>click</button>
    </>
  );
}

点击按钮修改state触发组件重渲染时,handleOnClick内的日志每次点击都会执行,而divRefCallback内的日志仅在组件首次渲染时执行一次。已知useCallback仅避免函数重建,无法阻止函数执行,疑惑为何ref回调既未重复创建也未重复执行,需明确以下内容:

  • 该现象的原因
  • useCallback阻止回调逻辑重复执行的其他场景
  • 相关官方文档说明

一、现象原因

React对ref回调函数的执行逻辑有特殊处理:

  • 当ref回调是稳定引用(比如用useCallback包裹且依赖为空)时,React只会在组件挂载时调用一次(传入DOM元素),卸载时调用一次(传入null)。
  • 组件重渲染时,只要ref回调的引用没有变化,React就不会重复调用它——因为DOM元素本身没有被销毁重建,无需再次执行回调更新ref指向。
  • 而onClick属于用户触发的事件监听,每次点击都会主动执行对应的事件处理函数,useCallback只是让函数引用稳定,不会改变事件触发时执行函数的逻辑。

二、useCallback阻止回调重复执行的其他场景

除了ref回调,以下场景中使用useCallback稳定函数引用后,也能避免回调重复执行:

  • 自定义组件的props回调:如果自定义组件用React.memo做了浅比较优化,当传入的回调引用稳定时,组件不会因回调变化重复渲染,进而避免回调不必要的执行(比如组件内部渲染时执行回调的情况)。
  • useEffect的依赖回调:如果useEffect的依赖是useCallback包裹的函数,函数引用稳定时不会触发useEffect重复执行,也就避免了回调在useEffect内的重复调用。
  • useImperativeHandle的回调:配合useImperativeHandle使用时,稳定的回调引用不会导致自定义ref的逻辑重复执行。

三、相关官方文档说明

React官方文档关于ref回调的核心说明:

React会在组件挂载时调用ref回调并传入DOM元素,在卸载时调用它并传入null。如果ref回调是以内联函数的方式定义的,在更新过程中它会被执行两次:第一次传入null,然后第二次传入DOM元素。但如果把ref回调定义成稳定引用(比如用useCallback包裹),就不会出现这种情况。

useCallback的作用是缓存函数引用,配合ref回调使用时,正好规避了内联函数导致的重复调用问题,让ref回调仅在挂载和卸载阶段执行。

内容的提问来源于stack exchange,提问作者flyingbee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 05:32:41