Modal 팝업 작업을 진행했습니다.
일단 공식문서에서 useRef, useImperativeHandle, forwardRef를 확인해보세요!
(저는 useRef로만 작업 했습니다.)
ko.reactjs.org/docs/hooks-reference.html#useref
ko.reactjs.org/docs/hooks-reference.html#useimperativehandle
ko.reactjs.org/docs/react-api.html#reactforwardref
useImperativeHandle과 forwardRef... 사용해서 해보려고도 하다가..
이것저것 하느라 시간이 걸렸습니다.
컴포넌트 형식 작업도 약간 헷갈리기도 하고^^;
그러다가 블로거님의 코드를 보고 짜잔 해결했습니다.
아래는 참고한 원본 코드의 주소입니다... 감사합니당(--)(__)(--)!
https://4log.hyeon.pro/post/click-event-outside-the-component
참고하여 코드 수정하여 적용한 모습입니다.
부모 컴포넌트입니다.
./Menu
import React, { useRef, useEffect, useCallback, useState } from "react";
import styled from 'styled-components';
import MenuPopup from './MenuPopup';
import { MenuOutlined } from '@ant-design/icons';
const MenuWrap = styled.div`
position: relative;
`;
const MenuButton = styled.button`
padding: 0;
background: none;
border: none;
cursor: pointer;
outline: none;
&:hover,
&:focus {
background: none;
}
&:hover,
&:focus,
&.active{
opacity: 0.5;
}
`;
const MenuIcon = styled(MenuOutlined)`
font-size: 17px;
color: ${props => props.themecolor};
`;
const Menu = ({ themecolor }) => {
const popRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
const onClickOutside = useCallback(({ target }) => {
if (popRef.current && !popRef.current.contains(target)) {
setIsOpen(false);
}
}, []);
const onClickMenu = useCallback(() => {
setIsOpen(!isOpen);
}, [isOpen]);
useEffect(() => {
document.addEventListener("click", onClickOutside);
return () => {
document.removeEventListener("click", onClickOutside);
};
}, []);
return(
<MenuWrap ref={popRef}>
<MenuButton onClick={onClickMenu}>
<MenuIcon themecolor={themecolor} />
</MenuButton>
<MenuPopup isOpen={isOpen} />
</MenuWrap>
);
};
export default Menu;
자식 컴포넌트입니다.
./MenuPopupWrap
import React from 'react';
import styled from 'styled-components';
const MenuPopupWrap = styled.div`
position: absolute;
top: 30px;
right: 0;
padding-top: 15px;
display: none;
width: 80px;
height: 125px;
background: rgba(0, 0, 0, 0.4);
clip-path: polygon(90% 10%,100% 10%,100% 100%,0 100%,0 10%,74% 10%,90% 0);
&.active {
display: block;
}
`;
const MenuPopup = ({ isOpen }) => {
return (
<MenuPopupWrap className={isOpen ? 'active' : ''}>
<ul>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
</ul>
</MenuPopupWrap>
);
};
export default MenuPopup;
(완성된 코드들은 아닙니다)
여기서 참고해야할 것을 간추리자면
부모컴포넌트에서..
const Menu = ({ themecolor }) => {
// 1. ref로 클릭한 타겟이 팝업인지 아닌지 체크할 것입니다.
const popRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
// 2. document에 바인딩할 클릭 이벤트입니다. (4번)
const onClickOutside = useCallback(({ target }) => {
if (popRef.current && !popRef.current.contains(target)) {
setIsOpen(false);
}
}, []);
// 3. 팝업 여닫는 이벤트입니다.
const onClickMenu = useCallback(() => {
setIsOpen(!isOpen);
}, [isOpen]); // 4. state를 넣어줘야 업데이트 됩니다.
// 4. 컴포넌트 마운팅될때 document 이벤트를(2번) 바인딩해줍니다.
useEffect(() => {
document.addEventListener("click", onClickOutside);
// 5. return을 통해 cleanup해줘야합니다.
// 안해주면 언마운팅이나 업데이트 시 문제가 생깁니다.
return () => {
document.removeEventListener("click", onClickOutside);
};
}, []);
return(
// 6. 오픈한 버튼이나 팝업 영역 모두 클릭 시 닫히면 안되기때문에...
// 전체 영역에 ref를 추가합니다.
<MenuWrap ref={popRef}>
// 7. 팝업 여닫는버튼입니다. (3번)
<MenuButton onClick={onClickMenu}>
<MenuIcon themecolor={themecolor} />
</MenuButton>
// 8. 팝업컨텐츠가 포함된 컴포넌트입니다.
<MenuPopup isOpen={isOpen} />
</MenuWrap>
);
};
export default Menu;
자식컴포넌트에서..
import React from 'react';
import styled from 'styled-components';
const MenuPopupWrap = styled.div`
display: none;
// 2. active일 경우 block처리하는 형식으로 했습니다.
// 이 부분은 부모 컴포넌트에서 처리해도됩니다.
&.active {
display: block;
}
`;
// 1. props를 받아와서 className을 추가합니다.
// 이 부분은 props를 통해 처리하지 않고 부모 컴포넌트에서 처리해도됩니다.
const MenuPopup = ({ isOpen }) => {
return (
<MenuPopupWrap
className={isOpen ? 'active' : ''}
>
<ul>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
</ul>
</MenuPopupWrap>
);
};
export default MenuPopup;
return 을 통해 뒷정리하는 부분 참고글!
velog.io/@velopert/react-hooks#23-%EB%92%B7%EC%A0%95%EB%A6%AC-%ED%95%98%EA%B8%B0