틸틸이 프로젝트를 진행하면서 반응형 웹을 추가로 구현하게 되었다. 미디어 쿼리를 사용하여 작업했지만, 조건에 따라 다른 헤더를 보여주는 작업들을 구현하려고 react-responsive 라이브러리를 사용하게 되었다.
// npm 설치
npm install react-responsive
// yarn 설치
yarn add 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훅을 사용하여 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;