프로젝트에서 메뉴 기능을 개발하면서 useRef 기능을 이 참에 잘 이해하고 써보려고 했다.
- 햄버거 모양의 메뉴 버튼을 클릭하면 → 메뉴가 펼쳐진다
- 햄버거 모양의 메뉴 버튼을 다시 클릭하면 → 메뉴가 닫힌다
- 펼쳐진 메뉴를 제외한 화면의 다른 부분을 클릭하면 → 메뉴가 닫힌다 👉 이걸 구현하고자 함!!
useState, useRef, useEffect 훅을 사용해서 메뉴의 열림/닫힘/메뉴 바깥 영역 클릭시 메뉴 닫힘 기능을 구현해보자.
const [isMenuOpen, setIsMenuOpen] = useState(false);
const menuRef = useRef(); // 드롭다운 메뉴에 대한 참조
먼저 useState 훅을 사용해서 메뉴의 열림/닫힘 상태를 나타내는 isMenuOpen 이라는 상태변수를 선언한다.
그리고 useRef 훅을 사용해서 menuRef 라는 참조 변수를 만든다.
이 menuRef 는 메뉴의 DOM 요소를 가리키게 된다.
function handleClickOutside(event) {
if (menuRef.current && !menuRef.current.contains(event.target)) {
setIsMenuOpen(false);
}
}
handleClickOutside 이라는 함수를 만들었다.
이 함수는 문서의 어느 곳을 클릭할 때 호출된다.
그리고 이 함수가 실행될 때 클릭이 menuRef 에서 일어났는지, 아니면 바깥에서 일어났는지 체크를 해줄거다.
만약 조건을 만족한다면, 메뉴 열림 상태 변수 false 로 바꿔주기!
🌱 if 조건문 톺아보기
menuRef.current 는 munuRef 를 할당한 요소를 직접 가리키게된다.
그래서 menuRef 를 참조하고 있는 DOM 요소가 실제로 마운트되어 있는지 검사하는 것이다.
그리고 !menuRef.current.contains(event.target) 은 사용자가 클릭한 요소(event.target) 이 menuRef.current 로 참조된 요소 내부에 있는지 여부를 확인한다.
이 코드에서는 메뉴 바깥을 클릭했는지 확인하는 것이기 때문에 ! 연산자를 붙였다.
정리하자면, 현재 메뉴가 화면에 마운트되어있는지, 그리고 클릭이 그 메뉴 바깥에서 일어났는지를 확인한다.
만약 위 조건을 모두 만족한다면, setIsMenuOpen 을 통해서 menuOpen 이라는 상태 변수를 false 로 만들어준다.
// 바깥 클릭을 감지하여 메뉴 상태 업데이트
useEffect(() => {
function handleClickOutside(event) {
if (menuRef.current && !menuRef.current.contains(event.target)) {
setIsMenuOpen(false);
}
}
// 바깥 클릭 이벤트 리스너 추가
document.addEventListener("mousedown", handleClickOutside);
return () => {
// 컴포넌트가 언마운트될 때 이벤트 리스너 제거
document.removeEventListener("mousedown", handleClickOutside);
};
}, [menuRef]);
useEffect 훅을 사용해서, 컴포넌트가 마운트될 때 document.addEventListener 를 호출해서 '전체'에 mousedown 이벤트 리스너를 추가한다.
-> 어디를 클릭하든 handleClickOutside 함수가 호출될 것이다.
위 useEffect 의 return 문으로 함수를 작성했다. (이 함수는 컴포넌트가 언마운트될 때 실행된다.)
이때 mousedown 이벤트리스너를 제거하도록 한다.
계속해서 이벤트 리스너가 실행되면 안되니까 컴포넌트가 언마운트 될 때는 이벤트 리스너를 제거해서, 메모리 누수와 성능 문제를 예방할 수 있을 것이다.
return(
...
<div ref={menuRef}>
<div
role="button"
onClick={() => setIsMenuOpen(!isMenuOpen)}
>
{isMenuOpen && (
<ul>
{/* 메뉴 항목 */}
</ul>
)}
이제 내가 원하는 div 에 menuRef 참조를 할당한다.
div 내의 요소에 onclick 이벤트 핸들러를 할당한다. 메뉴가 열려있으면 닫고, 메뉴가 닫혀있으면 열기 위한 것이다.
isMenuOpen 상태 변수의 값에 따라서, 메뉴를 숨기거나 연다.
아래는 위 내용을 적용한 전체 코드이다.
import { useState, useRef, useEffect } from 'react';
function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const menuRef = useRef(); // 드롭다운 메뉴에 대한 참조
// 바깥 클릭을 감지하여 메뉴 상태 업데이트
useEffect(() => {
function handleClickOutside(event) {
if (menuRef.current && !menuRef.current.contains(event.target)) {
setIsMenuOpen(false);
}
}
// 바깥 클릭 이벤트 리스너 추가
document.addEventListener("mousedown", handleClickOutside);
return () => {
// 컴포넌트가 언마운트될 때 이벤트 리스너 제거
document.removeEventListener("mousedown", handleClickOutside);
};
}, [menuRef]);
return(
...
<div ref={menuRef}>
<div
role="button"
onClick={() => setIsMenuOpen(!isMenuOpen)}
>
{isMenuOpen && (
<ul>
{/* 메뉴 항목 */}
</ul>
)}