최근 블로그에서 모달 관련 내용을 보았습니다.
저는 이전에 css를 통해서 구현하려고 하였는데, 이 부분은 번거롭고 효율적이지 못했습니다.
그래서 블로그 글을 참고하여 정리하려던 중 다른 방법으로 모달을 구현하는 것에 관해 알게되었습니다.
그래서 이 두 방법에 대해 고찰하면서 정리해보려고 합니다.
1.ReactDOM.createPortal 사용하여서 구현하기
2.Dialog를 통해 구현하기
3.장단점 비교 및 결론 도출
로 구성하였습니다.
본론으로 들어가기 전에 기존 프로젝트 형태를 살펴보아야합니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
react와 vite로 구현한다면 위와 같은 index.html을 볼 수 있습니다.
React는 <div id="root"></div>
에 종속하여 구현을 하는데, ReactDOM.createPortal는 root div에서 벗어나 구현할 수 있습니다.
이름처럼 포탈을 만들어주는데요 한번 살펴보겠습니다.
ReactDOM.createPortal(children, container);
children : 렌더링할 자식 컴포넌트
container : DOM element
렌더링할 컴포넌트를 children으로 넘겨주면, container에 렌더링을 도와줍니다.
우리는 모달을 구현하는 것이 목적이기 때문에, ReactDOM.createPortal에 맞추어 세팅을 해보겠습니다.
<body>
<div id="root"></div>
<div id="popup-root"></div>
<script type="module" src="/src/main.js"></script>
</body>
위 index.html에 <div id="popup-root"></div>
를 추가해주었습니다.
popup-root가 우리의 타겟입니다. 여기에 렌더링을 해주어서 모달을 구현해보겠습니다.
이제 React를 코드를 보겠습니다.
import { Global, css } from "@emotion/react";
import styled from "@emotion/styled";
import { createPortal } from "react-dom";
const Popup = ({ isOpen, closePopup, children }) => {
if (!isOpen) return null;
return (
<>
<Global
styles={css`
body {
overflow: hidden; // 스크롤 방지
}
`}
/>
{createPortal(
<PopupBackdrop onClick={closePopup}>{children}</PopupBackdrop>,
document.getElementById("popup-root") // 팝업을 렌더링할 DOM 노드를 지정합니다.
)}
</>
);
};
export default Popup;
createPortal을 사용하여 첫번째 인자에는 모달에 사용할 JSX와 2번째로 우리가 타겟할 popup-root로 설정하였습니다.
그리고 위 컴포넌트에 대해 설명하자면, emotion을 사용하고 있어서 global을 활용하여 스크롤 고정 및 해제를 넣었습니다.
props에 대해 설명하자면,
isOpen은 현재 모달 상태,
closePopup은 background 클릭을 통해 모달을 닫게 도와줄 함수,
children은 렌더링할 컴포넌트로 구성하였습니다.
우리는 위 컴포넌트에 모달 상태와 children을 넘겨주면 모달을 구현할 수 있습니다.
<div id="root"></div>
<div id="popup-root"></div>
우리가 popup 컴포넌트를 활용하여서 children을 넘겨주면 popup-root 자식으로 구현할 수 있습니다.
이 방법의 주의할 점은, 다른 div에 구현한다고 해서 최상위 레이어는 아닙니다.
그러니 추가적으로 두 div간의 z-index 관리는 필요로 합니다.
가장 좋은 개발은 구현하지 않는 것이고 있는 것을 활용하는 것이라고 생각합니다.
이점에서 tag를 활용한 개발은 매력적입니다.
daisyUI라는 라이브러리를 사용했는데, 여기서 사용하는 모달도 dialog로 구성해있다는 점에서 관심을 갖었습니다.
(좋은 UI를 구현하고 싶다면 MUI 같은 UI 컴포넌트 라이브러리를 살펴보는 것이 좋은 방법인 것 같습니다.)
본론으로 들어가면, dialog는 최상위 레이어를 구현하여 맨 앞쪽에 위치합니다.
최상위 레이어
최상위 레이어는 독립적인 레이어를 형성하여, 다른 요소들과 별개로 최상위에 배치한다.
최상위 레이어끼리 만나지만 않는다면 가장 앞에 서 있는 것을 보장할 수 있다는 점이 매력적이었습니다.
직접 구현을 해보니 HTML을 통해서 구현한다는 점은 좋았지만, 열고 닫고에는 JS에 의존적입니다.
dialog를 통해 웹 접근성도 고려할 수 있는 점은 매력적이었습니다.
tag는 간단해서 바로 구현해보도록 하겠습니다.
export default function Dialog({ isOpen, onBackdropClick = null, children }) {
const dialogRef = useRef(null);
useEffect(() => {
if (isOpen) {
dialogRef.current?.showModal(); // 모달 열기
} else {
dialogRef.current?.close(); // 모달 닫기
}
}, [isOpen]);
return (
<>
<Global styles={globalStyles(isOpen)} />
<dialog ref={dialogRef} css={dialogStyle}>
{children}
</dialog>
</>
);
}
const dialogStyle = (isOpen) => css`
display: ${isOpen ? "block" : "none"};
&::backdrop {
background: #fff5;
backdrop-filter: blur(4px);
}
`;
const globalStyles = (isOpen) => css`
${isOpen &&
`
body {
overflow: hidden;
}
`}
`;
마찬가지로 emotion을 통해 전체 스타일을 조절했습니다.
중요한 것은 dialog를 구현하면 최상위 레이어로 올라옵니다.
열고 닫는 것은 Ref를 통해 해당 tag에 연결해주고 showModal close를 통해 컨트롤 해줍니다.
구현을 하면 위처럼 top-layer라는 것을 확인할 수 있습니다.
확실히 컴포넌트 하나에서 관리할 수 있고 편리합니다.
좋았지만, 아쉬운 점이 있습니다.
브라우저에 제한적이라는 점입니다.
dialog Tag를 확인해보면, 호환성이 좋지 않습니다.
2022년도 safari인 것을 보면 아이폰을 3년이상 사용한 사람들을 위해 폴리필이 넣어주거나 하는 별도의 작업이 들어갑니다.
그래서 이는 PC전용 웹사이트에서는 추천하지만, 웹뷰에서는 제한적인 점이 아쉽습니다.
저의 결론은 createPortal을 권장합니다.
React 16버전부터 들어왔기 때문에, 호환성적인 측면에서 유리하다고 생각합니다.
하지만 몇년이 지나고 나면 브라우저 호환성이 개선되면서 dialog의 사용도 올라 갈 것이니 잊지는 말아야합니다.
오늘은 간단하게 dialog에 관해 정리해보았습니다.
제가 중요한 부분만 추려서 표현했기 때문에 세부적인 구현이 궁금하신 분들은
https://github.com/nakjun12/Market/blob/main/src/components/popup/Popup.js
(creatPortal)
https://github.com/nakjun12/Market/blob/main/src/components/popup/Dialog.js
(dialog Tag)
위 링크를 통해 확인해보시길 바랍니다.
저는 최근에 새로운 것을 배우는 것보다 제가 했던 내용들에 관해 정리해보는 시간을 갖을 생각이며, 아쉬운 점이 있다면 댓글이나 연락주시면 개선하도록 하겠습니다. 감사합니다~😊
출처 :
https://react-ko.dev/reference/react-dom/createPortal
https://developer.mozilla.org/ko/docs/Web/HTML/Element/dialog
https://leetrue.hashnode.dev/component-lab-modal
https://velog.io/@eunbinn/You-dont-need-JavaScript-for-that