如何在useCallback钩子中移除滚动事件监听器?
解决React滚动钩子的事件监听器移除问题
你的核心问题是用useCallback实现ref回调时,未处理旧元素的事件监听器移除,可能引发内存泄漏。以下是修正后的实现方案:
修正后的钩子代码
import { useState, useRef, useCallback } from 'react'; function useHasScrolled() { const [hasScrolled, setHasScrolled] = useState(false); const elementRef = useRef(null); // 稳定滚动处理函数的引用 const handleScroll = useCallback((e) => { setHasScrolled(e.target.scrollTop !== 0); }, []); const setRef = useCallback((element) => { // 移除旧元素的滚动监听 if (elementRef.current) { elementRef.current.removeEventListener('scroll', handleScroll); } // 给新元素绑定滚动监听 if (element) { element.addEventListener('scroll', handleScroll); } // 更新当前元素的引用 elementRef.current = element; }, [handleScroll]); return { hasScrolled, scrollingElementRef: setRef }; }
关键改进点
- 稳定处理函数引用:用
useCallback包裹handleScroll,确保添加和移除监听时使用同一个函数实例,否则removeEventListener无法生效。 - 清理旧元素监听:绑定新元素前,先检查
elementRef.current中保存的旧元素,移除它的滚动事件监听器,避免内存泄漏。 - 自动处理卸载场景:组件卸载时,React会自动调用ref回调并传入
null,此时代码会移除最后一个绑定元素的监听,完成清理。
使用方式(保持不变)
const { hasScrolled, scrollingElementRef } = useHasScrolled(); return ( <div ref={scrollingElementRef} style={{ overflow: 'auto', height: '300px' }}> {/* 滚动内容 */} </div> );
内容的提问来源于stack exchange,提问作者apieceofbart
相关产品推荐
相关产品推荐

