2024년 8월 14일 수요일

마우스 클릭과 더블클릭 구분하여 더블클릭 방지 처리 : JavaScript ES6 React

click 이벤트와 dblclick 이벤트를 사용하고, 클릭을 setTimeout으로 일정 시간 동안 지연시켜 더블클릭인지 단일 클릭인지 확인하는 방법을 사용한다.

JavaScript로 작성한 후 React 버전으로 수정 작성하였다.

모듈 파일 (handlers.js)

setupDblClickHandler 함수는 더블 클릭 시 클릭 타이머를 정리하는 역할만 수행한다.

// handlers.js

const clickTimeouts = new WeakMap();

export function setupClickHandler(element, handleClick) {
    element.addEventListener("click", function(event) {
        clearTimeout(clickTimeouts.get(element)); // 기존 타이머 클리어
        const timeout = setTimeout(function() {
            handleClick(event);
        }, 500); // 500ms 후에 클릭 이벤트로 처리
        clickTimeouts.set(element, timeout); // 새로운 타이머 저장
    });
}

export function setupDblClickHandler(element) {
    element.addEventListener("dblclick", function() {
        clearTimeout(clickTimeouts.get(element)); // 기존 타이머 클리어
    });
}

메인 파일 (main.js)

// main.js

import { setupClickHandler, setupDblClickHandler } from './handlers.js';

document.addEventListener("DOMContentLoaded", function() {
    // 예제 핸들러 함수
    function exampleClickHandler(event) {
        alert(`Single Click Detected on element: ${event.target.id}`);
    }

    // 요소를 동적으로 선택
    var button1 = document.getElementById("myButton1");
    var button2 = document.getElementById("myButton2");

    // 동적 핸들러를 각 요소에 설정
    setupClickHandler(button1, exampleClickHandler);
    setupClickHandler(button2, exampleClickHandler);
    setupDblClickHandler(button1);
    setupDblClickHandler(button2);
});

HTML 파일

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic Element and Handler</title>
</head>
<body>
    <button id="myButton1">Click or Double Click Me</button>
    <button id="myButton2">Another Button</button>

    <script type="module" src="main.js"></script>
</body>
</html>

코드 설명

  1. setupClickHandler: 클릭 이벤트가 발생하면 타이머를 설정하여 단일 클릭 이벤트를 처리한다.
    • 이전 타이머를 정리하고 새로운 타이머를 설정하여 500ms 후에 handleClick 함수를 호출한다.
  2. setupDblClickHandler: 더블 클릭 이벤트가 발생하면 타이머를 정리한다.
    • 클릭 타이머를 정리하여 단일 클릭 이벤트가 호출되지 않도록 한다.
  3. exampleClickHandler: 단일 클릭 이벤트 핸들러로, 클릭된 요소의 ID를 alert 창에 표시한다.
  4. HTML 파일: main.jstype="module"로 포함하여 모듈 시스템을 사용한다.

주의 사항

  • 이 구조에서는 더블 클릭 시 추가 로직을 처리하지 않는다. 만약 더블 클릭에 다른 동작을 추가하고 싶다면, setupDblClickHandler 함수를 다시 확장해야 한다.
  • 최신 브라우저 환경에서 작동하도록 설계되었기 때문에 구형 브라우저에서는 ES6 모듈과 WeakMap 지원을 확인해야 한다.

React에서 사용 가능하도록 수정한 버전 1

React에서 setupClickHandlersetupDblClickHandler 기능을 제공하는 방법으로, 커스텀 훅을 사용하여 단일 클릭과 더블 클릭을 구분하는 로직을 구현할 수 있다. 커스텀 훅은 재사용 가능한 로직을 추상화하고 컴포넌트에서 쉽게 사용할 수 있도록 도와준다.

아래는 useClickHandler라는 커스텀 훅을 작성하고 이를 React 컴포넌트에서 사용하는 예시이다.

1. useClickHandler 커스텀 훅 작성

hooks/useClickHandler.js

단일 클릭과 더블 클릭을 처리할 수 있는 useClickHandler 훅을 작성한다. 이 훅은 clickHandler를 인자로 받아서 클릭 이벤트와 더블 클릭 이벤트를 처리한다.

// hooks/useClickHandler.js

import { useRef, useEffect } from 'react';

export function useClickHandler(clickHandler) {
    const clickTimeoutRef = useRef(null);

    useEffect(() => {
        // 타이머를 클리어하는 함수
        const clearClickTimeout = () => {
            if (clickTimeoutRef.current) {
                clearTimeout(clickTimeoutRef.current);
                clickTimeoutRef.current = null;
            }
        };

        const handleClick = (event) => {
            clearClickTimeout();
            clickTimeoutRef.current = setTimeout(() => {
                clickHandler(event);
            }, 300); // 300ms 후에 클릭 이벤트로 처리
        };

        const handleDblClick = () => {
            clearClickTimeout(); // 더블 클릭 시 타이머 클리어
        };

        return { handleClick, handleDblClick };
    }, [clickHandler]);

    return { handleClick, handleDblClick };
}

2. React 컴포넌트에서 훅 사용

useClickHandler 훅을 사용하여 클릭 이벤트와 더블 클릭 이벤트를 처리하는 예제 컴포넌트를 작성한다.

components/ClickHandlerComponent.js

// components/ClickHandlerComponent.js

import React from 'react';
import { useClickHandler } from '../hooks/useClickHandler';

export function ClickHandlerComponent() {
    const { handleClick, handleDblClick } = useClickHandler((event) => {
        alert(`Single Click Detected on element: ${event.target.id}`);
    });

    return (
        <div>
            <button
                id="myButton1"
                onClick={handleClick}
                onDoubleClick={handleDblClick}
            >
                Click or Double Click Me
            </button>
            <button
                id="myButton2"
                onClick={handleClick}
                onDoubleClick={handleDblClick}
            >
                Another Button
            </button>
        </div>
    );
}

3. 앱 컴포넌트

React 앱에서 ClickHandlerComponent를 사용.

App.js

// App.js

import React from 'react';
import { ClickHandlerComponent } from './components/ClickHandlerComponent';

function App() {
    return (
        <div className="App">
            <h1>React Click and Double Click Handler</h1>
            <ClickHandlerComponent />
        </div>
    );
}

export default App;

4. 프로젝트 구조

/project-root
    ├── /src
    │    ├── /components
    │    │    └── ClickHandlerComponent.js
    │    ├── /hooks
    │    │    └── useClickHandler.js
    │    └── App.js
    ├── index.html
    └── index.js

코드 설명

  1. useClickHandler:
    • clickTimeoutRef를 사용하여 클릭 타이머를 관리한다.
    • handleClickhandleDblClick 함수는 각각 클릭과 더블 클릭 이벤트를 처리한다.
    • clearClickTimeout 함수는 타이머를 클리어하여 단일 클릭과 더블 클릭을 구분한다.
    • useEffectclickHandler가 변경될 때마다 새로 등록된 핸들러를 업데이트 한다.
  2. ClickHandlerComponent 컴포넌트:
    • useClickHandler 훅을 사용하여 클릭 및 더블 클릭 핸들러를 생성한다.
    • 각 버튼에 handleClickhandleDblClick을 이벤트 핸들러로 설정한다.
  3. App 컴포넌트:
    • ClickHandlerComponent를 사용하여 버튼 클릭 이벤트를 처리합니다.

추가 사항

  • 의존성 관리: useClickHandler 훅은 clickHandler가 변경될 때마다 타이머를 관리하도록 의존성을 설정한다.
  • 타이머 관리: 타이머는 useRef로 관리되어 컴포넌트가 리렌더링되더라도 상태가 유지된다.

React에서 사용 가능하도록 수정한 버전 2

React에서 이벤트 핸들러를 설정하고 단일 클릭과 더블 클릭을 구분하기 위해, Hook을 사용하여 상태를 관리할 수 있다. 아래 예시에서는 useEffectuseRef를 사용하여 DOM 이벤트를 설정하고, useState를 사용하여 클릭 타이머 상태를 관리한다.

1. Custom Hook (useClickHandler.js)

먼저, 클릭과 더블 클릭 핸들러를 설정하는 Custom Hook을 정의한다.

// useClickHandler.js

import { useEffect, useRef } from 'react';

const useClickHandler = (elementRef, handleClick) => {
  const clickTimeoutRef = useRef(null);

  useEffect(() => {
    const element = elementRef.current;

    const handleSingleClick = (event) => {
      clearTimeout(clickTimeoutRef.current);
      clickTimeoutRef.current = setTimeout(() => {
        handleClick(event);
      }, 300); // 300ms 후에 클릭 이벤트로 처리
    };

    const handleDoubleClick = (event) => {
      clearTimeout(clickTimeoutRef.current); // 더블 클릭 시 타이머 클리어
    };

    if (element) {
      element.addEventListener('click', handleSingleClick);
      element.addEventListener('dblclick', handleDoubleClick);
    }

    // Cleanup 이벤트 리스너
    return () => {
      if (element) {
        element.removeEventListener('click', handleSingleClick);
        element.removeEventListener('dblclick', handleDoubleClick);
      }
    };
  }, [elementRef, handleClick]);
};

export default useClickHandler;

2. 컴포넌트 파일 (MyComponent.js)

Custom Hook을 사용하여 단일 클릭 이벤트 핸들러를 설정하는 컴포넌트를 작성한다.

// MyComponent.js

import React, { useRef } from 'react';
import useClickHandler from './useClickHandler';

const MyComponent = () => {
  const buttonRef1 = useRef(null);
  const buttonRef2 = useRef(null);

  const handleClick = (event) => {
    alert(`Single Click Detected on element: ${event.target.id}`);
  };

  useClickHandler(buttonRef1, handleClick);
  useClickHandler(buttonRef2, handleClick);

  return (
    <div>
      <button id="myButton1" ref={buttonRef1}>Click or Double Click Me</button>
      <button id="myButton2" ref={buttonRef2}>Another Button</button>
    </div>
  );
};

export default MyComponent;

코드 설명

  1. Custom Hook (useClickHandler.js):
    • useClickHandlerelementRefhandleClick을 인자로 받는다.
    • clickTimeoutRefuseRef를 사용하여 클릭 타이머를 저장한다.
    • useEffect를 사용하여 컴포넌트가 마운트될 때 이벤트 리스너를 설정하고 언마운트될 때 정리한다.
    • 단일 클릭 이벤트를 처리하는 handleSingleClick과 더블 클릭 시 타이머를 정리하는 handleDoubleClick을 정의한다.
  2. 컴포넌트 파일 (MyComponent.js):
    • buttonRef1buttonRef2는 각각 두 버튼의 참조를 저장한다.
    • handleClick은 단일 클릭 이벤트 핸들러로, 클릭된 요소의 idalert로 표시한다.
    • useClickHandler를 사용하여 각 버튼에 대해 클릭 핸들러를 설정한다.
    • 버튼 요소에 ref 속성을 설정하여 DOM 요소를 참조할 수 있게 한다.

React 애플리케이션 구조

/src
    /components
        MyComponent.js
    /hooks
        useClickHandler.js
    index.js

React 애플리케이션 설정

index.js

애플리케이션의 진입점으로, MyComponent를 렌더링.

import React from 'react';
import ReactDOM from 'react-dom';
import MyComponent from './components/MyComponent';

ReactDOM.render(
  <React.StrictMode>
    <MyComponent />
  </React.StrictMode>,
  document.getElementById('root')
);

관련 사이트 및 블로그