드롭다운 구현

1Hoit·2023년 6월 27일

토이프로젝트

목록 보기
1/13
post-thumbnail

시작하며

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

위의 구현 코드

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;

동작 순서

  1. useRef를 이용해서 DropdownContainer라는 div 엘리먼트에 접근.
  1. document에 onCheckClickOutside함수를 가진 이벤트리스너를 달아준다.

  2. 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) 앞에 !를 붙여야 한다.

  • 더 자세히 알고 싶다면 여기를 확인해보자!

profile
프론트엔드 개발자를 꿈꾸는 원호잇!

0개의 댓글