이번에 토이 프로젝트를 진행하며 드롭다운을 구현하던 도중
드롭다운 메뉴 외부를 클릭하면 어떻게 드롭다운을 닫을지 고민하다가 작성하게 되었다!

import styled from 'styled-components';
import { IoMdArrowDropdown } from 'react-icons/io';
import { useState, useRef, useEffect } from 'react';
import { useNavigate } from 'react-router';
import { useRecoilValue, useResetRecoilState } from 'recoil';
import { curUser } from '../../recoil/signup';
import client from '../../api/axios';
const DropdownContainer = styled.div``;
const DropdownToggle = styled.div`
display: flex;
align-items: center;
cursor: pointer;
.people-icon {
background-color: var(--gray-600);
border-radius: 50%;
width: 45px;
height: 45px;
}
`;
const DropdownMenu = styled.ul<{ dropdown: boolean }>`
display: ${({ dropdown }) => (dropdown ? 'flex' : 'none')};
position: absolute;
flex-direction: column;
justify-content: space-around;
top: 105%;
right: 0;
width: 185px;
height: 140px;
background-color: var(--dark-blue-900);
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
`;
const DropdownItem = styled.li`
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2rem;
color: var(--light-gray-200);
:hover {
color: var(--white-100);
transform: scale(1.02, 1.02);
}
`;
const Dropdown = () => {
// 드롭다운이 열려 있는지 닫혀 있는지 확인하는 상태
const [dropdown, setDropdown] = useState<boolean>(false);
// useRef를 이용해서 드롭다운 DOM 에 접근
const dropdownRef = useRef<HTMLDivElement>(null);
const currentUser = useRecoilValue(curUser);
const clearCurUser = useResetRecoilState(curUser);
const navigate = useNavigate();
//드롭다운 상태 변경
const handleClick = () => {
setDropdown(!dropdown);
};
useEffect(() => {
// 드롭다운의 외부를 클릭했을 때 발생할 함수 작성
const outsideClick = (e: MouseEvent) => {
console.log(e.target); // 마우스를 어느곳에든 클릭하면 어디의 DOM 요소를 클릭한지 확인할 수 있다.
//!dropdownRef.current.contains(e.target as Node)
//DropdownContainer 태그 영역에 이벤트가 발생하지 않았을때를 의미
if (dropdown === true && dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setDropdown(false); //외부 클릭시 실행할 로직 (드롭다운의 상태 변경)
}
};
//컴포넌트 렌더링 후 이벤트 등록해야한다.
//mousedown 이벤트가 발생하면 outsideClick함수가 실행된다.
document.addEventListener('mousedown', outsideClick);
return () => {
// Cleanup the event listener
document.removeEventListener('mousedown', outsideClick); //컴포넌트가 제거될때 실행
};
}, [dropdown]);
return (
<DropdownContainer ref={dropdownRef}>
<DropdownToggle onClick={handleClick}>
<img src={currentUser.profile} alt='profile-icon' className='people-icon'></img>
<IoMdArrowDropdown className='down-icon' size='24' color='var(--light-gray-200)'></IoMdArrowDropdown>
</DropdownToggle>
<DropdownMenu dropdown={dropdown}>
<DropdownItem onClick={() => navigate('/main')}>내 블로그</DropdownItem>
<DropdownItem onClick={() => navigate('/mypage')}>나의 정보</DropdownItem>
<DropdownItem>로그아웃</DropdownItem>
</DropdownMenu>
</DropdownContainer>
);
};
export default Dropdown;
document에 onCheckClickOutside함수를 가진 이벤트리스너를 달아준다.
onCheckClickOutside 함수는 DropDown 메뉴가 열렸을때, ropdownRef에 이벤트가 주어지지 않을 때를 제외하고 전 영역을 클릭시 DropDown 메뉴가 닫힌다.
외부 클릭을 위한 마우스 이벤트
mousedown와 click 차이
- mousedown
- html element를 클릭하는 순간 이벤트가 동작합니다.
- click
- html element를 클릭이 끝나는 순간 이벤트가 동작합니다.
- html element를 클릭하고 있는 상태에서 html element를 벗어나고 클릭을 떼는 경우 click 이벤트는 동작하지 않습니다.
코드 분석
dropdownRef.current.contains(e.target as Node)
현재 이벤트를 실행한 element가 this.dropdownRef.current에 포함이 되지 않으면 false, 포함되거나 동일하다면 true이다.
외부를 클릭했을 경우 동작해야 하므로
dropdownRef.current.contains(event.target)앞에 !를 붙여야 한다.더 자세히 알고 싶다면 여기를 확인해보자!