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
相关产品推荐
相关产品推荐

