모달창 외부 영역을 클릭하면 모달창이 꺼질 수 있게 구현하고 싶었다.
이 기능은 outside click
이며, 이를 구현하려면 다음과 같은 개념들을 알아야 한다.
- 이벤트 버블링
- ref
- DOM contains method
이벤트가 제일 깊은 곳에 있는 요소에서 시작해, 부모 요소를 거슬러 올라가며 발생하는 현상.
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
가장 안쪽의 <p>
를 클릭하면 순서대로 다음과 같은 일이 벌어진다.
<p>
-><div>
-><form>
<p>
에 할당된 onclick 핸들러가 동작한다.<div>
에 할당된 핸들러가 동작한다.<form>
에 할당된 핸들러가 동작한다.event.target
은 실제 이벤트가 시작된 타깃 요소이다. 버블링이 진행되어도 변하지 않는다.event.currentTarget(=this)
는 현재 요소로, 현재 실행중인 핸들러가 할당된 요소를 참조한다.event.target
라면, 이 요소를 감싸는 큰 틀이 event.currentTarget(=this)
라고 할 수 있겠다.
EventTarget.addEventListener(eventType, 함수)
removeEventListener
를 명시해야 종료할 수 있다.document.addEventListener
: 전체 document 페이지에 대한 이벤트이므로, 특정 컴포넌트 안에서만 발생된다는 착각은 금물!!특정 노드나 컴포넌트에 레퍼런스 값을 만들어 주는 것입니다. 때문에
ref
를 통해서 노드나 리액트 요소에 접근하여 값을 얻어낼 수 있습니다.
createRef
useRef
함수형 컴포넌트에서 useRef
를 사용하려면 import와 별도의 호출을 해주어야 한다.
import React, { useEffect, useRef } from 'react';
export default function Header() {
const modalRef = useRef();
return (
<div className="ModalPopup" ref={modalRef}>
...
</div>
);
}
- 문서 전체에 이벤트 리스너를 붙인다.
mousedown
mousedown
: 클릭하는 순간 이벤트가 동작된다.click
: 클릭이 끝나는 순간 이벤트가 동작한다.
- outside 클릭의 기준이 되는 HTML 요소를 잡아준다.
React ref
이용
- 클릭 이벤트
mousedown
가 발생시,event.target
이ref
에 저장된 요소를 포함하는지 확인한다.
- 포함이 되지 않는다면, outside 클릭이 되는 것이다.
import React, { useEffect, useRef } from 'react';
import './Header.scss';
export default function Header({
btnClick,
btnName,
isClickFilterBtn,
children,
}) {
const modalRef = useRef();
useEffect(() => {
document.addEventListener('mousedown', clickModalOutside);
return () => {
document.removeEventListener('mousedown', clickModalOutside);
};
});
const clickModalOutside = event => {
if (btnClick && !modalRef.current.contains(event.target)) {
isClickFilterBtn();
}
};
return (
<div className="ModalPopup" ref={modalRef}>
<header className="modalHeader">
<span className="modalReset">
<i className="fas fa-redo" />
초기화
</span>
<h1 className="modalName">{btnName}</h1>
<span className="modalClose" onClick={isClickFilterBtn}>
X
</span>
</header>
{children}
</div>
);
}
useRef
사용을 위해 import와 별도의 호출을 해주었다.import React, { useEffect, useRef } from 'react';
const modalRef = useRef();
<div className="ModalPopup">
이다. 클릭의 기준이 될 요소 자체이므로, 여기에 ref={modalRef}
를 달았다.useEffect
에 사용자의 클릭에 반응할 document.addEventListener
를 달아주었다. 사용자의 클릭 mousedown
이 발생할 때마다 clickModalOutside()
메서드가 호출된다.document.addEventListener
는 스스로 종료되지 않는다. 만약 제거해주지 않으면, 전에 실행되었던 이벤트가 이번에 또 실행되고, 그 다음에 또 실행되는 이벤트가 축적된다. 따라서 useEffect
에 언마운트 (=cleanup 함수)처리를 해주었고, document.removeEventListener
로 이벤트리스너를 종료하였다.💡 축적된 이벤트들이 완전히 제거되지는 않는다는 것을 console.log
로 찍어보았다. 언마운트 처리를 해주지 않았을 때보다는 축적된 이벤트들이 확실히 적었다.
useEffect
에서는 함수를 반환할 수 있는데, 이를 cleanup 함수라고 부릅니다. cleanup 함수는useEffect
에 대한 뒷정리를 해준다고 이해하시면 되는데요, 컴포넌트가 사라질 때 cleanup 함수가 호출됩니다.
clickModalOutside
메서드는 클릭 이벤트가 발생될 때마다 실행되는 메서드이다. 클릭 이벤트가 발생되면 모달창이 꺼지는 로직을 처리해주었다. const clickModalOutside = event => {
if (btnClick && !modalRef.current.contains(event.target)) {
isClickFilterBtn();
}
};
btnClick
은 모달창이 켜지면 true
, 꺼지면 false
가 되는 state
값이다. 따라서 모달창이 true
일 때만 성립된다.false
가 되므로 조건문이 성립하지 않는다. 따라서 모달창이 꺼지지 않는다.true
가 되므로 조건문이 성립한다. 따라서 모달창이 꺼진다.
📚 감사한 출처
https://react.vlpt.us/basic/16-useEffect.html
https://ko.javascript.info/bubbling-and-capturing