최근 프로젝트에서 모달 컴포넌트를 사용하는데 많은 불편함을 느껴 React Portal을 사용하게 되었습니다.
제가 프로젝트를 진행하면서 겪은 불편함을 간단하게 정리했으니 필요하신 분들은 아래 예시를 확인해 주세요😊
어떤 불편함이 있었냐구요🤔
아래 사진은 chrome의 개발자 도구를 통해 확인한 모달입니다.
단순히 detph가 깊어지는데 무슨 불편함이 있는지 궁금하실 수 있겠네요❗
위 예시를 보시면 DOM의 계층 구조에 의해 모달의 z-index에 문제가 생긴 경우입니다.
import styled from "styled-components";
function PortalExample() {
const Title = styled.h1`
position: fixed;
top: 0;
left: 0;
z-index: 1;
background-color: #fff;
`;
return <Title>Portal 예시입니다!</Title>;
}
function App() {
const Container = styled.div`
position: relative;
width: 100vw;
height: 100vh;
`;
const Background = styled.div`
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
background-color: #000;
`;
return (
<Container>
React Portal Example
<Background />
<PortalExample />
</Container>
);
}
export default App;
h1과 div의 z-index가 1로 같은데, 계층 구조에 의해 h1이 안 보이네요.
h1을 상위 계층으로 끌어올리면 어떻게 될까요🤔🤔
상위 계층으로 끌어올리니 z-index 문제가 해결되었네요❗
상위 계층으로 요소를 끌어올린다고 z-index가 더 작아도 되는 것은 아닙니다.
하지만 z-index 문제를 우회하거나 이벤트 버블링을 제어하는 등 어느 정도 유리한 점은 있겠죠👀
React Portal을 선택한 이유💡
React로 프로젝트를 진행하다 보면 부모 DOM 계층구조를 무시하고 다른 DOM 노드에 컴포넌트를 렌더링해야 할 때가 있습니다. 제 경우에는 모달을 body 하위에 렌더링 하기 위해 React Portal을 선택했습니다.
React Portal이란?
React Portal은 React 컴포넌트를 DOM 트리의 다른 위치에 렌더링 할 수 있도록 해주는 기능입니다. 일반적인 React 컴포넌트는 부모 컴포넌트의 DOM 트리에 렌더링되지만, Portal을 사용하면 부모 DOM 트리 외부에 컴포넌트를 렌더링할 수 있습니다.
React Portal 사용법
createPortal(children, domNode)
: createPortal을 호출하여 일부 JSX와 렌더링할 DOM 노드를 전달합니다.
import { createPortal } from 'react-dom';
{createPortal(
<p>This child is placed in the document body.</p>,
document.body
)}
- children : 렌더링할 React 노드 (React 엘리먼트, 문자열, 숫자 등).
- domNode : 렌더링할 DOM 노드
이제 Modal을 만들고 React Portal을 사용해 보겠습니다❗
Modal 컴포넌트 생성
import { createPortal } from "react-dom";
import styled from "styled-components";
function Modal({ children }) {
const Modal = styled.div`
position: fixed;
top: 0;
bottom: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
`;
const ModalOverlay = styled.div`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
`;
const ModalContent = styled.div`
overflow: hidden;
position: relative;
z-index: 1;
max-width: 1000px;
width: 80%;
max-height: 80%;
height: fit-content;
padding: 20px;
border-radius: 2rem;
background-color: #fff;
`;
return createPortal(
<Modal>
<ModalOverlay />
<ModalContent>{children}</ModalContent>
</Modal>,
document.body
);
}
export default Modal;
Modal 컴포넌트를 App 컴포넌트에서 import해 사용해 보겠습니다.
어떤 결과가 나올까요👀
function App() {
const Container = styled.div`
position: relative;
width: 100vw;
height: 100vh;
`;
return (
<Container>
React Portal Example
<Modal>모달 컴포넌트</Modal>
</Container>
);
}
Modal 컴포넌트가 body 하위에 잘 렌더링 되었네요👍
모달이 렌더링 되는 것을 확인했으니, 열고 닫는 기능도 추가해 보겠습니다.
모달은 자주 사용하는 컴포넌트이다 보니 커스텀 훅으로 만들어 볼게요❗❗
useModal 커스텀 훅 생성
import { useState } from "react";
function useModal() {
const [isOpen, setIsOpen] = useState(false); // 모달의 열고 닫힘을 관리하기 위한 state
const openModal = () => setIsOpen(true); // 모달 열기
const closeModal = () => setIsOpen(false); // 모달 닫기
return {
isOpen,
openModal,
closeModal,
};
}
export default useModal;
useModal 커스텀 훅을 만들었으니, Modal에 적용해야겠죠😁
import { createPortal } from "react-dom";
import styled from "styled-components";
function Modal({ children, isOpen, closeModal }) {
{/* 스타일은 생략 하겠습니다. */}
if (!isOpen) return null; // isOpen이 false인 경우 null을 리턴, 모달 렌더링 X
return createPortal(
<Modal>
<ModalOverlay onClick={closeModal} /> // Dim 처리 된 영역을 클리하면 모달이 닫히도록 클릭 이벤트 추가
<ModalContent>{children}</ModalContent>
</Modal>,
document.body
);
}
export default Modal;
function App() {
const { isOpen, openModal, closeModal } = useModal();
{/* 스타일 생략 */}
return (
<Container>
<button type='button' onClick={openModal}>
모달 열기
</button>
<Modal isOpen={isOpen} closeModal={closeModal}>
모달 컴포넌트
</Modal>
</Container>
);
}
export default App;
모달을 열고 닫는 기능이 잘 동작하네요!
앞으로 진행하는 프로젝트에서 유용하게 사용할 수 있을 것 같습니다❗
React Portal을 사용해 보고 느낀 점📕
- 유연한 DOM 구조 : DOM 계층을 벗어나 렌더링이 가능해 UI 구현이 매우 편리하다.
- CSS 스타일 충돌 최소화 : 부모 컴포넌트의 스타일 영향을 받지 않아 독립적인 디자인이 가능해 편리하다.
- 학습 곡선이 낮음 : React에서 기본으로 제공하는 기능이고, API가 간단해서 처음 사용해도 쉽게 적응할 수 있었다.
'개발 공부 일지 > React' 카테고리의 다른 글
[React] Emotion - React 컴포넌트 스타일링 (0) | 2025.01.06 |
---|---|
[React] 리액트 라이프사이클(Lifecycle)과 useEffect (6) | 2024.12.26 |
[React] React Hook Form 라이브러리로 Form 간편하게 관리하기 (2) | 2024.11.27 |
[React] Compound Component Pattern (4) | 2024.11.15 |
[React] Render Props Pattern (2) | 2024.11.14 |