React中onTouch与onMouse事件同时触发致函数重复执行问题
问题原因
问题出在第三方组件的实现逻辑上:它将你传入的onClick回调同时绑定到了onMouseDown和onTouchStart两个事件上。在支持触摸的设备中,浏览器会在触发touchstart事件后自动模拟mousedown事件,导致两个事件先后触发,最终让你的handlePrevious执行两次。
你之前尝试的preventDefault()和stopPropagation()无效,是因为这些方法需要在事件触发的第一时间调用——而你是在最终的handlePrevious里调用,此时touchstart已经执行完毕,浏览器已经在准备触发mousedown了,自然无法阻止重复执行。
解决方案
根据你是否能修改第三方组件,提供两种处理思路:
1. 若能修改/二次封装第三方组件
方案一:改用原生onClick事件
直接去掉onMouseDown和onTouchStart的绑定,恢复原生onClick——它在触摸和鼠标设备上都能正常触发,且不会有重复执行问题:
type FooterIconButtonType = typeof FooterIconButtonInt; export const FooterIconButton: FooterIconButtonType = (props: ComponentProps<FooterIconButtonType>) => { return ( <FooterIconButtonInt {...props} onClick={props.onClick} onMouseDown={undefined} onTouchStart={undefined} /> ); };
方案二:添加重复触发拦截
在组件内部用标志位控制,确保短时间内只执行一次回调:
import { useRef } from 'react'; type FooterIconButtonType = typeof FooterIconButtonInt; export const FooterIconButton: FooterIconButtonType = (props: ComponentProps<FooterIconButtonType>) => { const isTriggered = useRef(false); const handleEvent = (e: React.MouseEvent | React.TouchEvent) => { if (isTriggered.current) return; isTriggered.current = true; props.onClick?.(e); // 300ms后重置标志,避免后续点击失效,可根据需求调整时长 setTimeout(() => { isTriggered.current = false; }, 300); }; return ( <FooterIconButtonInt {...props} onClick={undefined} onMouseDown={handleEvent} onTouchStart={handleEvent} /> ); };
2. 若无法修改第三方组件(仅在主应用处理)
方案一:用标志位拦截重复执行
在handlePrevious内部通过useRef记录是否已执行,避免短时间内重复触发:
import { useCallback, useRef } from 'react'; // ... const handlePrevious = useCallback(() => { const hasExecuted = useRef(false); return () => { if (hasExecuted.current) return; hasExecuted.current = true; // do some stuff here!! setTimeout(() => { hasExecuted.current = false; }, 300); }; }, [])();
方案二:拆分事件并阻止默认行为
覆盖组件的事件绑定,分别处理touchstart和mousedown,在touchstart中调用preventDefault()阻止浏览器触发mousedown:
const handleTouchStart = (e: React.TouchEvent) => { e.preventDefault(); // 阻止浏览器模拟mousedown handlePrevious(); }; const handleMouseDown = () => { handlePrevious(); }; // 使用组件时 <FooterButtonsWrapper center style={{zIndex: 2, position: 'relative'}}> <FooterIconButton hide={isGrid} src={AppIcons.backSmall} onClick={undefined} // 覆盖原组件的onClick绑定 onMouseDown={handleMouseDown} onTouchStart={handleTouchStart} /> </FooterButtonsWrapper>
内容的提问来源于stack exchange,提问作者user25441075
相关产品推荐
相关产品推荐

