24. 메뉴 드롭다운 구현 + useState + onClick

random-olive·2023년 2월 15일
0

프로젝트 01 : 

목록 보기
22/25
  • width: 768px 이상일 때는 hover하면 나타나는 메뉴를 나오게 하고

  • 그 이하 (mobile size)일 때는 로고를 클릭하면 드롭다운 메뉴가 나오게 하려고 한다.

1. styled-component로 간단히 드롭다운 구현

  • 원하는 위치에서 dropdown되게 하려면 position:absolute를 사용해야 하는데, absolute는 부모의 position을 기준으로 위치를 선정하는 prop으로, 부모의 position이 relative면 그걸 토대로 위치를 선정한다. 부모의 position이 설정되어 있지 않으면 화면 왼쪽 상단을 기준으로 위치를 잡기 시작한다.
<DropDown>         (1) 드롭다운 메뉴의 기준점: relative
  <MenuBar>        (2) 메뉴 리스트를 한 번에 담음 : absolute
      <ul>         (3) 메뉴 리스트
        <li>1</li>
        <li>2</li>
      </ul>
  </MenuBar>
</DropDown>
const DropDown = styled.button`
  position: relative;          (1)
  width: 100px;
`;

const MenuBar = styled.div`
  position: absolute;          (2)
  display:none;                (4)
  ${DropDown}:active & {       (5)
    display: block;
  }
  ${DropDown}:focus & {        (5)
    display: block;
  }
`;
  • DropDown은 button으로 만들어 클릭에 따라 active한지 체크하게 한다.
  • (4) : 메뉴 리스트는 처음에 display:none으로 보이지 않다가,
  • (5) : DropDown 컴포넌트가 활성화되면 display:block으로 보여주기 시작한다. ${컴포넌트} : {} 문법은 아주 간편했다.
// 하지만 styled-component로 작성된 형태는 가능하지만
const DropDown = styled.div`
...
`
//다음과 같은 형태는 불가능했다.

const DropDown = () => {
return (
...)
}

2. useState를 활용한 드롭다운

  • 복잡한 컴포넌트를 드롭다운으로 활용하기 위해서 다음과 같이 코드를 작성했다.
//Layouts.tsx
export const BasicLayout = () => {
  return (
    <>
      <DropdownHeader />
      {window.outerWidth < 768 ? '' : <MenuBar />}
      <Outlet />
      <Footer />
    </>
  );
};
  • 768px 이상이면 horizontal 메뉴바를 띄운다.
  • 드롭다운 메뉴바인 DropdownHeader 컴포넌트는 그 위에 항상 띄운다.
//LogoAndSearch.tsx
export const DropdownHeader = () => {
  return (
    <Vertical>
      <LogoContainer>
        <HomeLogo
          margin={window.outerWidth < 768 ? '0' : RESPONSIVE.HEADER_MARGIN}
        />
        {window.outerWidth < 768 ? <DropdownBar display='block' /> : ''}
      </LogoContainer>
      <Searchbar />
    </Vertical>
  );
};
  • 드롭다운 메뉴바는 LogoContainer로 이루어져 있는데, 이 안에는 HomeLogo와 DropdownBar로 이루어져 있다.
  • HomeLogo의 위치를 relative로 한다.
//MenuBar.tsx
export const DropdownBar: React.FC<BarProp> = ({ display }) => {
  return (
    <Vertical>
      <MenuContainer display={display}>
        {mainMenu[0].list.map((el, idx) => (
          <Main key={idx}>{el.title}</Main>
        ))}
      </MenuContainer>
    </Vertical>
  );
};
  • DropdownBar 내부의 MenuContainer 컴포넌트의 위치를 absolute로 한다. Main 컴포넌트는 메뉴블럭 하나하나를 뜻하는데 만약 Main의 위치를 absolute로 한다면 모든 메뉴가 한 곳에 겹쳐 나오기 때문에 Main들을 묶어서 absolute prop을 가져줄 MenuContainer 컴포넌트가 필요한 것이다.

  • 이제 클릭할 때마다 useState로 상태에 따라 display의 prop을 block / none으로 결정하면 드롭다운 메뉴가 활성화된다.

3. useState + onClick type

//LogoAndSearch.tsx
import {useState} from 'react';

interface ClickProp {
  onClick?: () => void; (1)
}

export const DropdownHeader: React.FC<ClickProp> = () => { (1)
  const [menuActive, setMenuActive] = useState<boolean>(false);  (2)
  const toggle = () => {setMenuActive(!menuActive)}; (3)
  
  return (
    <Vertical>
      <LogoContainer onClick={toggle}> (4)
        <HomeLogo
          margin={window.outerWidth < 768 ? '0' : RESPONSIVE.HEADER_MARGIN}
        />
        {window.outerWidth < 768 ? (
          <DropdownBar display={menuActive ? 'block' : 'none'} /> 
        ) : (
          ''
        )} (5)
      </LogoContainer>
      <Searchbar />
    </Vertical>
  );
};
  • (1) : onClick prop을 사용하기 위해서 interface나 type을 설정한다.
    만약 설정하지 않는다면 🟥"type {onClick:()=>void} is not assignable" 과 같은 에러가 발생한다. TS에서는 함수 실행 후 별도의 return이 없으면 undefined 대신 void라는 데이터 타입을 반환한다. 그렇기 때문에 void를 type으로 설정해준다.
  • (2) : menuActive 자체는 false / true로 boolean이므로 타입을 제네릭으로 지정해준다.
  • (3), (4) : LogoContainer 컴포넌트를 클릭하면 set함수가 menuActive를 토글시킨다.
  • (5) : 드롭다운 바 자체는 menuActive가 되어있으면 'block'을 prop으로 받아 화면에 나타내고, 아닐 경우는 'none'을 prop으로 받아 화면에서 사라진다.
profile
Doubts kills more dreams than failure ever will

0개의 댓글