빵부스러기

[TypeScript] 셀렉트 버튼 외부를 클릭했을 때, 이벤트 감지하는 방법

borobong230 2024. 2. 7. 18:55
import { useState } from "react"

const Button = () => {
  const [isOpen, setOpen] = useState<boolean>(false)

  const handleOpen = () => {
    setOpen(!isOpen)
  }

  return (
    <button type="button" onClick={handleOpen}>
      {isOpen ? "열렸다" : "닫혔다"}
    </button>
  )
}

export default Button

 



외부에서 클릭하는 걸 감지 하고 싶다면?

useRef 사용하기!

import { useEffect, useRef, useState } from "react"

const Button = () => {
  const [isOpen, setOpen] = useState<boolean>(false)

  const ref = useRef<HTMLButtonElement>(null)

  const handleOpen = () => {
    setOpen(!isOpen)
  }

  // 외부 이벤트 감지용
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        setOpen(false)
      }
    }

    document.addEventListener("mousedown", handleClickOutside)

    return () => {
      document.removeEventListener("mousedown", handleClickOutside)
    }
  }, [])

  return (
    <button type="button" onClick={handleOpen} ref={ref}>
      {isOpen ? "열렸다" : "닫혔다"}
    </button>
  )
}

export default Button



좀 더 생각해볼 점

`useEffect` isOpen Depandency

 

방법 1. 

useEffect(() => {
  const handleClickOutside = (event: MouseEvent) => {
    if (ref.current && !ref.current.contains(event.target as Node)) {
      setOpen(false)
    }
  }
  document.addEventListener("mousedown", handleClickOutside)
  return () => {
    document.removeEventListener("mousedown", handleClickOutside)
  }
}, [])



방법 2.

useEffect(() => {
  const handleClickOutside = (event: MouseEvent) => {
    if (isOpen && ref.current && !ref.current.contains(event.target as Node)) {
      setOpen(false)
    }
  }
  document.addEventListener("mousedown", handleClickOutside)
  return () => {
    document.removeEventListener("mousedown", handleClickOutside)
  }
}, [isOpen])



방법 1의 문제점.

 

외부 클릭시 매번 아래의 함수 실행됨.
-> 불필요한 호출 발생?

 

 if (ref.current && !ref.current.contains(event.target as Node)) {
  setOpen(false)
}

 

방법 2의 문제점.

버튼이 클릭 될 때 마다 (isOpen이 변경될때마다), 이벤트가 등록/삭제가 발생합니다.

 


결론

사실 브라우저가 이러한 연산을 효율적으로 처리할 수 있기 때문에 대부분 문제는 되지 않습니다.

`isOpen` 상태가 자주 변경되지 않고, 드롭다운의 개방 상태 관리가 성능에 미치는 영향이 크게 우려되지 않는다면, 방법 1이 더 나을 수 있을 것 같습니다.
반면에 코드의 단순성과 일관된 성능을 우선시한다면, 방법 2가 더 나은 선택일 것 같습니다.

굳이~~ 나의 상황에서 둘중에 하나를 고르자면 방법 2를 고를 것 같습니다.
제 경우는 버튼을 많이 누를 일이 없기 때문입니다.

 

하지만 이것은 모든 상황에 해당되는 것은 아닙니다.

버튼의 용도에 따라서 방법 1이 더 좋은 방법일 수 있습니다.

 

본인의 상황에 맞는 방법을 사용해보세요!