[React] react-responsive 반응형 라이브러리, 드롭다운 메뉴

jungmin Lee·2023년 7월 16일
0


틸틸이 프로젝트를 진행하면서 반응형 웹을 추가로 구현하게 되었다. 미디어 쿼리를 사용하여 작업했지만, 조건에 따라 다른 헤더를 보여주는 작업들을 구현하려고 react-responsive 라이브러리를 사용하게 되었다.

react-responsive 설치 및 사용

react-responsive 설치

// npm 설치
npm install react-responsive
// yarn 설치
yarn add react-responsive

react-responsive 사용

useMediaQuery 훅을 사용한 미디어 쿼리
useMediaQuery를 통해서 화면 크기나 미디어 쿼리 조건에 따라 컴포넌트를 조건적으로 렌더링할 수 있다. 브라우저의 창 크기에 따라 데스크톱 뷰와 모바일 뷰를 조건부로 렌더링하였다.

import { useMediaQuery } from 'react-responsive';

export default function MyComponent() {
  const isDesktopOrLaptop = useMediaQuery({ query: '(min-width: 1224px)' })
  const isMobile = useMediaQuery({ query: '(min-width: 767px)' });

  return (
    <div>
      {isDesktopOrLaptop && <p>데스크톱 뷰</p>}
      {isMobile && <p>모바일 뷰</p>}
    </div>
  );
};

미디어 쿼리 컴포넌트
Media 컴포넌트로 미디어 쿼리 조건에 따라 렌더링 할 수 있다.

import MediaQuery from 'react-responsive'

export default function MyComponent() (
  <div>
    <MediaQuery minWidth={1224}>
      <p>desktop or laptop</p>
    </MediaQuery>
  </div>
)

장치 유형별 컴포넌트
Mobile, Tablet, Desktop 컴포넌트를 사용하여 장치 유형에 따라 컴포넌트를 조건적으로 렌더링할 수 있다.

import { Mobile, Tablet, Desktop } from 'react-responsive';

export default function MyComponent() {
  return (
    <div>
      <Mobile>
        <p>모바일 뷰</p>
      </Mobile>
      <Tablet>
        <p>태블릿 뷰</p>
      </Tablet>
      <Desktop>
        <p>데스크톱 뷰</p>
      </Desktop>
    </div>
  );
};

프로젝트에 적용하기

useMediaQuery 미디어 쿼리

useMediaQuery훅을 사용하여 max-width 800이라는 조건이 맞으면 모바일헤더를 보여주기로 설정하고, 그 조건이 아닐 경우에는 기본 헤더를 보여주는 것으로 설정하였다. 조건에 맞게 컴포넌트를 보여줄 수 있어서 반응형을 조금 더 쉽게 구현할 수 있었다.

App.js

import { useMediaQuery } from 'react-responsive';

function App() {
  const isMoblie = useMediaQuery({ query: '(max-width: 800px)' });

  return (
    <>
      <Modal />
      {isMoblie ? <MobileHeader /> : <Header />}
    </>
  );
}

export default App;

드롭다운 메뉴

dropdown 조건에 따라 버튼을 클릭할 때 버튼의 아이콘도 변경하고, useState를 사용하여 dropdown 상태를 관리하며 드롭다운 메뉴 컴포넌트를 보여주도록 구현하였다. dropdown menu는 따로 컴포넌트로 만들어서 코드의 가독성을 높이고 유지보수가 쉽도록 구현하였다.

MobileHeader.js

import { Link } from 'react-router-dom';
import {
  HeaderWrapper,
  InnerWrapper,
  TextLogo,
  NavLogo,
} from '../../default/styled';
import { ReactComponent as Menu } from '../../default/image/menu.svg';
import { ReactComponent as Close } from '../../default/image/close.svg';
import { useState } from 'react';
import Dropdown from './Dropdown';

function MobileHeader() {
  const [dropdown, setDropdown] = useState(false);
  const OpenDropdown = () => {
    setDropdown(!dropdown);
  };

  const CloseDropdown = () => {
    setDropdown(false);
  };

  return (
    <>
      <HeaderWrapper>
        <InnerWrapper flex>
          <NavLogo>
            <Link to="/">
              <TextLogo onClick={CloseDropdown}>TilTile</TextLogo>
            </Link>
          </NavLogo>
          {dropdown ? (
            <button onClick={OpenDropdown}>
              <Close />
            </button>
          ) : (
            <button onClick={OpenDropdown}>
              <Menu />
            </button>
          )}
        </InnerWrapper>
      </HeaderWrapper>
      {dropdown ? <Dropdown closeMemu={CloseDropdown} /> : null}
    </>
  );
}

export default MobileHeader;

Dropdown.js

import { useNavigate, useLocation } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import useStore from '../../default/useStore';
import { useEffect, useState } from 'react';
import API from '../../API';

export const NavStyleMobile = styled(NavLink)`
  color: var(--color-black);
  padding: 10px 0px 10px;
  font-weight: 600;
  font-size: 12px;
  :hover {
    color: var(--brand-color);
  }
`;

export const DropdownWrapper = styled.div`
  position: fixed;
  display: flex;
  box-sizing: border-box;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  margin-top: 64px;
  padding: 10px 50px;
  width: 100%;
  height: 220px;
  background-color: var(--color-white);
  border-top: 1px solid var(--color-title-linegray);
  border-radius: 0px 0px 10px 10px;
  box-shadow: rgb(0 0 0 / 10%) 0px 4px 12px;
  z-index: 2;
`;

export const DropdownBorder = styled.div`
  width: 100%;
  padding: 10px 0px 20px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--color-title-linegray);
`;

function Dropdown({ closeMemu }) {
  const { isLogin, setLoginStatus } = useStore();
  const navigate = useNavigate();
  const location = useLocation();
  const [profileData, setProfileData] = useState(null);

  {/* ...생략 */}

  return (
    <DropdownWrapper>
      <NavStyleMobile
        to="/til/list"
        onClick={() => handleNavigation('/til/list')}
      >
        탐색
      </NavStyleMobile>
      <NavStyleMobile
        to="/til/hotlist"
        onClick={() => handleNavigation('/til/hotlist')}
      >
        핫틸
      </NavStyleMobile>
      <DropdownBorder>
        <NavStyleMobile to="/write" light onClick={closeMemu}>
          TIL 작성하기
        </NavStyleMobile>
      </DropdownBorder>

      {isLogin ? (
        <>
          {profileData && (
            <NavStyleMobile to="/profile/mytil" onClick={closeMemu}>
              마이페이지
            </NavStyleMobile>
          )}
          <NavStyleMobile onClick={handleLogout}>로그아웃</NavStyleMobile>
        </>
      ) : (
        <>
          <NavStyleMobile to="/account/login" onClick={closeMemu}>
            로그인
          </NavStyleMobile>
          <NavStyleMobile to="/account/signup" outline onClick={closeMemu}>
            회원가입
          </NavStyleMobile>
        </>
      )}
    </DropdownWrapper>
  );
}

export default Dropdown;
profile
Leejungmin

0개의 댓글