需求
在写前端项目的时候,经常会涉及到单击和双击操作。比如一个实际的需求:在连连看游戏中(左边是英文单词按钮,右边是中文释义按钮):单击按钮可以实现连线操作;双击已经连好线的按钮,就取消连线。在这个实际的需求中,我们可以看到单击和双击执行的是不一样的操作。
困难
一般来说,触发双击事件都会触发单击事件,因为双击本质上就是两次较短间隔的单击组成的。比如点击按钮的有一个event.detail,指的是点击按钮的次数
// 单击的时候
1
//双击的时候
1
2
可以看到在输出2之前,输出了一个1,这说明在双击之前,单击首先发生。同样在react中我们虽然提供了单击事件onClick,双击事件onDoubleClick。我开始以为这两个已经说的很清楚了吧,一个处理单击的,以及处理双击的,直到我给按钮加上了这两个事件,并且在处理函数中分别简单console.log一下。。。
function App(){
const handlerAClick = ()=>{
console.log("单击事件")
}
const handlerBClick = ()=>{
console.log("双击事件")
}
return (
<button onClick={handlerAClick}
onDoubleClick={handlerBClick}>
Click me </button>
)
}
//输出
// 单击的时候
>>> 单击事件
//双击的时候
>>> 单击事件
>>> 单击事件
>>> 双击事件
似乎现成的事件都没有双击和单击分的很清楚,也是人家的 click是点击的意思,又不是双击的意思。
解决办法
后面通过在网上查阅资料,一般都是使用定时器来进行区分的,。主要思路就是:还是绑定点击事件,不过需要设置一个状态 clickCount, 对点击次数进行记录。在第一次点击的时候,设置一个定时器【设置一定的间隔,一般是300ms或500ms,不超过1s】,里面放单击时候的操作;在第二次点击的时候,说明已经是双击了, 这个时候将之前设置的定时器清除,这样就不会执行单击的操作,接着在后面就可以写双击后的操作逻辑了,具体代码见下
function App(){
// 记录点击次数,设置定时器
const [clickCount, setClickCount] = useState<number>(0);
let clickTimer: NodeJS.Timeout | null = null;
const handleClick = () => {
setClickCount(prevCount => prevCount + 1);
if (clickCount === 0) {
// 第一次点击,启动定时器
clickTimer = setTimeout(() => {
// 这里感觉写不写都不影响
if (clickTimer) {
clearTimeout(clickTimer);
clickTimer = null;
}
// 处理单击逻辑
console.log('单击');
// 注意这里要重置
setClickCount(0);
}, 500); // 设置一个延迟时间,以便在延迟期间内判断单击还是双击
} else if (clickCount === 1) {
// 第二次点击,清除定时器
if (clickTimer) {
clearTimeout(clickTimer);
clickTimer = null;
}
// 处理双击事件的逻辑
console.log('双击');
// 重置点击次数
setClickCount(0);
}
}
return (
<button onClick={handleClick}
Click me </button>
)
}
感觉这样没问题吧,思路啥的,都挺说的通吧。但是,拿着这份代码去试试,会发现,怎么双击的时候,输出的双击后面还跟个单击呀,这不又没用了吗。通过调试,我发现在else里面的clickTimer是null,也就是说双击的时候根本没有清除定时器,这个时候根本不存在定时器呀,那为啥后面单击里面的逻辑还能执行呢???感觉我们似乎没有访问到真正的定时器,虽然我也不明白为啥没有。
但是有一种可行的方法,就是使用useRef,这样是能保证正确访问到定时器的
function App(){
// 记录点击次数,设置定时器
const [clickCount, setClickCount] = useState<number>(0);
const clickTimerRef = useRef<NodeJS.Timeout | null>(null);
const handleClick = () => {
setClickCount(prevCount => prevCount + 1);
if (clickCount === 0) {
// 第一次点击,启动定时器
clickTimerRef.current = setTimeout(() => {
// 这里感觉写不写都不影响
if (clickTimerRef.current) {
clearTimeout(clickTimerRef.current);
clickTimerRef.current = null;
}
// 处理单击逻辑
console.log('单击');
// 注意这里要重置
setClickCount(0);
}, 500); // 设置一个延迟时间,以便在延迟期间内判断单击还是双击
} else if (clickCount === 1) {
// 第二次点击,清除定时器
if (clickTimerRef.current) {
clearTimeout(clickTimerRef.current);
clickTimerRef.current = null;
}
// 处理双击事件的逻辑
console.log('双击');
// 重置点击次数
setClickCount(0);
}
}
return (
<button onClick={handleClick}
Click me </button>
)
}
至此,就可以实现单双击的完全分离啦!