시작하기
햄버거 버튼을 누르면 사이드 메뉴가 나오고, 사이드 메뉴 내부에 있는 x 표시나 사이드메뉴 외부를 누르면 사이드메뉴가 닫히는 기능을 구현한다.
버튼 누르면 사이드메뉴가 나오기
버튼을 누르면 사이드 메뉴가 나오게 하는 것은 간단하다. 우선 헤더와 사이드메뉴 이렇게 두 개의 컴포넌트를 준비한다.
그리고 햄버거 버튼에 클릭 이벤트를 걸어주고, 나는 useState 변수를 사용해서 구현했다.
Header.tsx
function Header() {
const [isOpen, setIsOpen] = useState(false);
const toggleSide = () => {
setIsOpen(true);
};
return (
<HeaderPostion>
<MenuBtn role="button" onClick={toggleSide}>
<MenuImg src="/img/menu.png" />
</MenuBtn>
<Sidebar isOpen={isOpen} setIsOpen={setIsOpen} />
</HeaderPostion>
);
}
1. 사이드 메뉴 컴포넌트를 Header안에 같이 넣어줬다. 파라미터로 isOpen과 set함수를 같이 넘겨준다.
2. 햄버거 버튼을 누르면 isOpen 변수를 true로 변경해준다.
Sidebar.tsx (일부분)
const toggleSide = () => {
setIsOpen(false);
};
return (
<SideBarWrap id="sidebar" ref={outside} className={isOpen ? 'open' : ''}>
<img
src="/img/close.png"
alt="close"
onClick={toggleSide}
onKeyDown={toggleSide}
/>
<ul>
<Menu>메뉴1</Menu>
<Menu>메뉴2</Menu>
<Menu>메뉴3</Menu>
</ul>
</SideBarWrap>
);
사이드메뉴에서는 x 이미지에 클릭 이벤트를 걸어준다. 이 클릭 이벤트는 사이드 메뉴 창을 닫아야하기 때문에 setIsOpen 함수를 사용해서 isOpen 변수를 false로 설정해준다. 그리고 className은 삼항 연산자를 사용해서 isOpen 값에 따라서 open 클래스를 추가/삭제 해준다.
style 컴포넌트
const SideBarWrap = styled.div`
z-index: 5;
padding: 12px;
border-radius: 15px 0 0 15px;
background-color: #e7e4e1;
height: 100%;
width: 55%;
right: -55%;
top: 0;
position: fixed;
transition: 0.5s ease;
&.open {
right: 0;
transition: 0.5s ease;
}
`;
style을 보면 우선 fixed와 right 속성을 사용해서 처음에는 오른쪽에 숨어있도록 한다.
그리고 만약 open 이라는 클래스 이름이 추가되면 right를 0으로 설정해서 보일 수 있게 한다.
외부 컴포넌트 클릭 시 사이드메뉴 닫기
외부 컴포넌트를 클릭하면 메뉴가 닫히도록 하기 위해서 useRef 함수를 사용한다.
여기서 중요한 개념이 이벤트 버블링이다.
이벤트 버블링이란?
이벤트가 제일 깊은 곳에 있는 요소부터 시작해서 부모 요소로 거슬러 올라가면서 발생하는 현상이다.
<div onClick={clickEventD}>
<span onClick={clickEventS}>
<p onClick={clickEventP}>
</p>
</span>
</div>;
이런식으로 코드가 있을 때 p태그의 내용을 클릭하면 clickEventP -> clickEventS -> clickEventD 순서로 이벤트가 발생한다.
가장 최상단에 있는 요소를 만날 때까지 요소를 타고 올라가면서 각각의 요소에 할당된 이벤트가 발생한다.
이 순서를 잘 기억하고 있어야 동작 원리를 이해할 수 있다.
useRef()
특정 요소에 참조값을 만들어준다. 태그 안에 ref 속성을 사용해서 참조할 수 있다.
const outside = useRef<any>();
useRef객체를 만들어주고
<SideBarWrap id="sidebar" ref={outside}>
이렇게 요소에 ref 속성을 사용해서 선언해주면, outside 변수를 통해서 해당 요소를 계속 레퍼런스 할 수 있다.
이 두 가지를 사용해서 외부 컴포넌트 클릭시에 메뉴가 닫히도록 구현할 수 있다.
순서는 다음과 같다.
1. 먼저 위와 같이 메뉴 컴포넌트에 useRef를 사용해서 참조값을 만들어준다.
2. document에 이벤트를 등록해서 화면을 클릭할 때마다 메뉴 컴포넌트 안인지 밖인지 판단해준다.
3. 밖을 클릭한 경우면 메뉴를 닫아준다.
useEffect(() => {
document.addEventListener('mousedown', handlerOutsie);
return () => {
document.removeEventListener('mousedown', handlerOutsie);
};
});
const handlerOutsie = (e: any) => {
if (!outside.current.contains(e.target)) { //현재 클릭한 곳이 메뉴 컴포넌트 안이 아니면 닫기
toggleSide();
}
};
const toggleSide = () => {
setIsOpen(false);
};
여기서 왜 이벤트 버블링이 중요하냐면 document에 이벤트를 등록할 때 아무생각 없이 click 이벤트로 정하면 안된다.
mousedown 이벤트 : 클릭 하는 순간
click 이벤트 : 클릭이 끝나는 순간
이 두개의 차이를 알아야한다.
예를 들어 설명하자면 나같은 경우는 메뉴버튼을 "click" 이벤트를 사용해서 메뉴가 열리도록 구현해놨다.
그런데 만약 document에 "click" 이벤트로 닫는 것을 구현해 놓는다면
이벤트 버블링에 의해서 메뉴버튼의 "click" 이벤트가 실행되고
부모 요소를 타고 올라가면서 document의 "click" 이벤트가 발생할 것이다.
다시말해서 열었다가 바로 닫아버려서 아무일도 일어나지 않는다.
그래서 본인이 어떤식으로 구현했냐에 따라서 click인지 mousedown인지 잘 선택해서 구현해야한다.
Sidebar.tsx 전체 코드
import React, { useRef, useEffect } from 'react';
import styled from 'styled-components';
const SideBarWrap = styled.div`
z-index: 5;
padding: 12px;
border-radius: 15px 0 0 15px;
background-color: #e7e4e1;
height: 100%;
width: 55%;
right: -55%;
top: 0;
position: fixed;
transition: 0.5s ease;
&.open {
right: 0;
transition: 0.5s ease;
}
`;
const Menu = styled.li`
margin: 30px 8px;
`;
const ExitMenu = styled.span`
position: absolute;
bottom: 26px;
font-size: 0.8rem;
`;
function Sidebar({ isOpen, setIsOpen }: { isOpen: boolean; setIsOpen: any }) {
const outside = useRef<any>();
useEffect(() => {
document.addEventListener('mousedown', handlerOutsie);
return () => {
document.removeEventListener('mousedown', handlerOutsie);
};
});
const handlerOutsie = (e: any) => {
if (!outside.current.contains(e.target)) {
toggleSide();
}
};
const toggleSide = () => {
setIsOpen(false);
};
return (
<SideBarWrap id="sidebar" ref={outside} className={isOpen ? 'open' : ''}>
<img
src="/img/close.png"
alt="close"
onClick={toggleSide}
onKeyDown={toggleSide}
/>
<ul>
<Menu>메뉴1</Menu>
<Menu>메뉴2</Menu>
<Menu>메뉴3</Menu>
</ul>
</SideBarWrap>
);
}
export default Sidebar;
참고 링크
'개발 공부 > React' 카테고리의 다른 글
[React+TS] React에서 비로그인처리, PrivateRoute사용하기 (0) | 2022.11.15 |
---|---|
[React+TS] useNavigate props 전달과 useLocation으로 props 받기/ useLocation unknown 타입 (0) | 2022.07.01 |