React中如何让wheel事件每次滚动仅触发一次?
解决React中wheel事件多次触发的问题
你的问题主要来自两个点:一是wheel事件本身的特性——不管是滚轮还是触控板,一次滚动动作会连续触发多次wheel事件;二是你的useEffect依赖了trackpadSwipe和pageNumber,每次状态更新都会重新绑定事件监听器,导致多个监听器同时生效,触发次数翻倍。
直接给你修改后的代码,附关键改动说明:
import { useEffect, useState, useCallback } from 'react'; // 防抖函数:指定时间内只执行一次回调 const debounce = (func, delay) => { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); }; }; function YourComponent() { const [pageNumber, setPageNumber] = useState(1); // 若没有其他业务依赖,可直接去掉trackpadSwipe状态 // const [trackpadSwipe, setTrackpadSwipe] = useState('none'); // 用useCallback缓存处理函数,避免每次渲染重新创建 const handleTrackpadSwipe = useCallback(debounce((event) => { // 忽略微小滚动,过滤误触 if (Math.abs(event.deltaY) < 10) return; if (event.deltaY > 0) { console.log('swiped down'); // setTrackpadSwipe('swiped down'); if (pageNumber < 4) { // 函数式更新确保拿到最新状态值,避免闭包问题 setPageNumber(prev => prev + 1); } // setTrackpadSwipe('none'); } else if (event.deltaY < 0) { console.log('swiped up'); // setTrackpadSwipe('swiped up'); if (pageNumber > 1) { setPageNumber(prev => prev - 1); } // setTrackpadSwipe('none'); } }, 300), [pageNumber]); // 300ms防抖间隔,可按需调整 useEffect(() => { window.addEventListener('wheel', handleTrackpadSwipe); // 必须返回清理函数,避免重复绑定监听器 return () => { window.removeEventListener('wheel', handleTrackpadSwipe); }; }, [handleTrackpadSwipe]); return ( <div style={{ overflow: 'hidden', height: '100vh' }}> 当前页码:{pageNumber} </div> ); } export default YourComponent;
关键改动说明:
- 防抖函数过滤重复事件:通过
debounce限制一段时间内只处理一次滚动,过滤掉触控板/滚轮连续触发的多余事件。 - 清理事件监听器:
useEffect返回清理函数,组件卸载或依赖变化时移除旧监听器,避免重复绑定。 - 函数式更新状态:用
setPageNumber(prev => prev + 1)替代直接依赖pageNumber,解决闭包导致的状态值滞后问题。 - 缓存处理函数:通过
useCallback缓存防抖后的处理函数,减少不必要的useEffect触发。 - 移除冗余状态操作:原代码中
setTrackpadSwipe刚设置就清空,若无其他业务依赖可直接删除。
内容的提问来源于stack exchange,提问作者kobo
相关产品推荐
相关产品推荐

