오늘 토이 프로젝트를 진행하면서 메뉴 버튼 밖을 클릭했을 때 메뉴 컴포넌트가 닫히는 UI를 구현하고 싶었다.
방법에 대해 한참을 고민하다 보니... 최상의 컴포넌트에서 props drilling을 통해 메뉴를 닫는 기능을 구현할까... 하는 생각이 들었지만
어엿한 프론트엔드 개발자를 꿈꾸는 나에게 이것은 올바른 해결법이 아니라는 판단이 들었다.

그래서 구글링을 해보니 해당 방법을 Effect 훅과 Ref 훅을 이용해 해결하는 방법에 대해 참고할 수 있었다.
전체 코드는 이러한데 한번 부분 부분 살펴보자.
const Deck = ({data}: Props) => {
const [openInnerMenu, SetOpenInnerMenu] = useState<boolean>(false);
const InnerMenuHandler = (event: React.MouseEvent) => {
SetOpenInnerMenu(!openInnerMenu);
event.preventDefault();
}
const ref = useRef<any>();
useEffect(()=>{
const handleClickOutside=(event: any)=>{
if (ref && !ref.current.contains(event.target)) {
SetOpenInnerMenu(false);
}
}
document.addEventListener('click', handleClickOutside);
return () => {
document.removeEventListener('click', handleClickOutside);
};
}, [ref])
return (
<DeckContainer>
<DeckWrapper>
<MenuContainer ref={ref} className={openInnerMenu ? 'focus':''}>
<FaEllipsisV size={20} onClick={InnerMenuHandler}/>
{openInnerMenu ? <InnerMenu props={InnerMenuHandler}/> : null}
</MenuContainer>
<div>
<div>
{data.name}
</div>
<div>
{data.description}
</div>
</div>
</DeckWrapper>
<ShadowOne />
<ShadowTwo />
</DeckContainer>
)
}
export default Deck;
처음에는 다소 이해하기 어려운 코드들인데 요약하자면 아래와 같은 원리이다.
- 최상위 객체인 document에 click 이벤트 리스너를 붙인다.
- 해당 이벤트 리스너는 클릭 이벤트가 발생했을 때 handleClickOutside라는 함수를 호출한다.
document.addEventListener('click', handleClickOutside);
- handleClickOutside 함수는 이벤트를 인자로 받는다.
- if 문에서 ref.current.contains() 메서드는 node API로 만약 event.target 노드가 ref.current 노드에 포함되어있다면 true값을, event.target 노드가 ref.current 노드에 포함되어 있지 않다면 false값을 반환한다.
- 즉 쉽게 말해 지금 클릭한 노드가 ref 노드인지 ref 노드가 아닌지 감지한다는 뜻.
- ref node가 아닐 시 메뉴를 닫는 useState 메서드 호출
const handleClickOutside=(event: any)=>{
if (ref && !ref.current.contains(event.target)) {
SetOpenInnerMenu(false);
}
}
- 마지막으로 이벤트 리스너를 제거함으로써 메모리 리크를 방지한다.
return () => {
document.removeEventListener('click', handleClickOutside);
};
이렇게 하면 원하는 기능이 잘 구현되는 것을 확인할 수 있다. 하지만 몇 가지 의문점이 풀리지 않는 부분이 있다.
1. 아래 코드에서 ref 를 삭제하고! ref.current~ 부분만을 넣어도 정상적으로 작동한다. 아무래도 앞의 ref는 ref가 정상적으로 있는지를 판별하기 위해 넣은 코드인 듯한데 해당 부분에 관련된 설명을 찾을 수는 없었다.
if (ref && !ref.current.contains(event.target))
2. useEffect의 의존성 배열에 일단은 ref를 넣어두긴 했지만 안넣어도 기능 구현에는 전혀 문제가 발생하지 않았다. 이 부분에 대해서 이해를 완벽히 못하는 이유은 ref 훅에 대한 이해도가 떨어지기 때문인 것 같다. 이번 주말에는 ref 훅에 대해 집중적으로 공부를 해보아야 할 듯하다.
}, [ref])
'Front-End > React' 카테고리의 다른 글
[React] Controlled vs Uncontrolled component (0) | 2022.06.21 |
---|---|
[React] 웹 개발에서 리엑트를 사용하는 이유 (0) | 2022.06.03 |
[React] dotenv 사용시 주의할 점 (0) | 2022.04.05 |
[React] React는 MVC 패턴일까 Flux 패턴일까? (0) | 2022.03.29 |
[React] State vs Props (0) | 2022.03.28 |