react中单双击操作的判别

860 阅读3分钟

需求

在写前端项目的时候,经常会涉及到单击和双击操作。比如一个实际的需求:在连连看游戏中(左边是英文单词按钮,右边是中文释义按钮):单击按钮可以实现连线操作;双击已经连好线的按钮,就取消连线。在这个实际的需求中,我们可以看到单击和双击执行的是不一样的操作。

困难

一般来说,触发双击事件都会触发单击事件,因为双击本质上就是两次较短间隔的单击组成的。比如点击按钮的有一个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, 对点击次数进行记录。在第一次点击的时候,设置一个定时器【设置一定的间隔,一般是300ms500ms,不超过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里面的clickTimernull,也就是说双击的时候根本没有清除定时器,这个时候根本不存在定时器呀,那为啥后面单击里面的逻辑还能执行呢???感觉我们似乎没有访问到真正的定时器,虽然我也不明白为啥没有。 但是有一种可行的方法,就是使用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>
    )
}

至此,就可以实现单双击的完全分离啦!