Tech/ReactJS

외부 클릭 시 연관 검색어 창 닫기 : addEventListener 사용

닝닝깅 2024. 3. 29. 14:51

검색어 입력 중 검색창 외부를 클릭하면 연관 검색어 창이 닫히도록 구현해야 할 일이 생겼다

비슷한 경험으로 과거 모달을 띄우고 모달 외부를 클릭하면 모달이 닫히도록 구현한 적이 있는데,

그때는 어두운 불투명 오버레이 컴포넌트를 깔고 오버레이 컴포넌트에 onClick 이벤트 핸들러를 할당하여 클릭 이벤트가 발생하면 모달을 닫히는 동작으로 간단하게 구현할 수 있었다.

 

이번에도 동일한 방법으로 투명 오버레이를 깔아 구현해볼까 생각했으나

연관 검색어 창이 활성화 된 상태에서도 지도 확대 축소나 스크롤 등의 이벤트는 정상 동작 해야했기 때문에 불가능하다고 판단하였다.

 

💭 생각한 두가지 방법

그래서 생각해본 두가지 방법..!

우선 연관 검색어 창이 열려있는 지 닫혀있는지를 boolean으로 나타내는 state값을 하나 만들어두고...

 

1. usestate만 활용

기존 아이디어에서 착안한 방법이다.

오버레이 컴포넌트 대신 전체 화면을 감싸는 컴포넌트에 onClick 이벤트 핸들러를 할당하여 state 값이 false가 되어 연관 검색어 창이 닫히도록 한다.

 

2. addEventListener 활용

useRef를 만들어 current에 엘리먼트를 담고 addEventListener를 이용하여 해당 엘리먼트가 아닌 곳을 클릭했을 때 state값이 false가 되도록 한다.

 

✅ 나의 선택은..!

useState만 활용하는 방법이 간편하게 구현할 수 있고

addEventListener를 사용하며 발생할 수 있는 메모리 누수 가능성을 없앨 수 있다는 점에서 가장 끌리는 방법이었다.

 

하지만 프로젝트 구조상 컴포넌트를 작은 단위로 분리시켜두었기 때문에 props drilling이 심해질 것 같아 두번째 방법을 선택하게 되었다. 검색 버튼을 눌러야지만 검색 컴포넌트가 마운트 되는 것이기 때문에 걱정하였던 메모리 누수의 정도도 줄일 수 있을 것 같았다.

 

🖇 구현 내용

const relatedRef = useRef<HTMLDivElement>(null);
const [relatedResultShow, setRelatedResultShow] = useState(false);

const clickResultOutside = (e: any) => {
  const currentRef = relatedRef.current;
  if (currentRef && !currentRef?.contains(e.target as Node)) {
    setRelatedResultShow(false);
  }
};

1. useRef() 를 사용하여 ref 객체를 만든다.

2. 이 객체를 선택하고 싶은 DOM ( 나의 경우에는 연관 검색창 컴포넌트 )에 ref 값으로 설정해준다.

3. 해당 dom이 아닌 영역 선택 시 state값을 false로 변경하는 이벤트 핸들러를 생성한다.

-> event.target ref에 저장된 요소를 포함하는지 확인하고 포함하지 않으면 state값이 false로 변경되도록 동작!

useEffect(() => {
    document.addEventListener("click", clickResultOutside);
    return () => {
      document.removeEventListener("click", clickResultOutside);
    };
}, [relatedRef.current]);

4. 컴포넌트가 마운트 된 순간부터 실행될 수 있도록 useEffect를 활용하여 이벤트 리스너를 등록한다.

5. 성능향상을 위해  포넌트의 마운트가 해제될 때 이벤트도 삭제되도록 리턴 값에 removeEventListener로 이벤트를 삭제시킨다.