https://ko.legacy.reactjs.org/docs/portals.html
React portal이란?
리액트에서 일반적으로 컴포넌트는 부모 요소 안에 렌더링된다. 하지만 Portal을 사용하면 특정 DOM 요소에 렌더링할 수 있다. 즉, JSX 구조상으로는 부모 안에 있지만, 실제 DOM에서는 완전히 다른 곳에 렌더링 되도록 하는 것이다.
왜 필요할까?
예를 들어보자. 기본적으로 modal, tooltip 같은 UI 요소는 화면 최상단 레이어에 떠야 한다. 그런데 부모 컨테이너가 overflow:hidden과 같은 스타일이 적용되면 잘려서 보이지 않는 문제가 발생할 수 있다. 이런 문제를 portal로 해결할 수 있다.
코드로 살펴보자
먼저, 두가지 상황을 코드로 구현한 것이다. 한개의 modal은 portal을 적용하지 않아 modal의 일부분이 hidden 처리가 될 것이고, 한개의 modal은 portal이 적용되어 최상단에 전체가 다 보일 것이다.
개발자 도구도 함께 살펴 보았는데, portal을 적용하지 않은 modal은 부모 밑에 렌더링 되었지만, 적용한 modal은 body 안에 렌더링 된 것을 확인할 수 있었다.
// App.jsx
import React, { useState } from "react";
import Modal from "./Modal"; // ✅ Portal 사용 X (부모 안에서 렌더링됨)
import PortalModal from "./PortalModal"; // ✅ Portal 사용 O (body에 렌더링됨)
export default function App() {
const [isOpen, setIsOpen] = useState(false);
const [isPortalOpen, setIsPortalOpen] = useState(false);
return (
<div
style={{
overflow: "hidden", // ✅ 부모가 overflow: hidden을 가짐!
position: "relative",
height: "200px",
width: "200px",
backgroundColor: "orange",
}}
>
<h2 style={{ fontSize: "14px" }}>부모 요소 (overflow: hidden 적용됨)</h2>
{/* 일반 모달 */}
<button onClick={() => setIsOpen(true)}>기본 모달 열기</button>
{isOpen && (
<Modal onClose={() => setIsOpen(false)}>
<span>일반 모달</span>
</Modal>
)}
{/* Portal을 사용하는 모달 */}
<button
onClick={() => setIsPortalOpen(true)}
style={{ marginLeft: "10px" }}
>
포탈 모달 열기
</button>
{isPortalOpen && (
<PortalModal onClose={() => setIsPortalOpen(false)}>
<span>포탈 모달</span>
</PortalModal>
)}
</div>
);
}
// Modal.jsx
import React from "react";
const Modal = ({ children, onClose }) => {
return (
<div
style={{
position: "absolute", // 부모 기준으로 위치 결정됨
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "250px",
height: "120px",
background: "rgba(0, 0, 0, 0.8)",
color: "#fff",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "8px",
zIndex: 10, // 부모의 z-index에 영향을 받을 수 있음
}}
>
{children}
<button onClick={onClose} style={{ marginLeft: "10px" }}>
닫기
</button>
</div>
);
};
export default Modal;
// PortalModal.jsx
import React from "react";
import ReactDOM from "react-dom";
const PortalModal = ({ children, onClose }) => {
return ReactDOM.createPortal(
<div
style={{
position: "fixed", // ✅ 항상 화면 기준으로 배치됨
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "250px",
height: "120px",
background: "rgba(0, 0, 0, 0.8)",
color: "#fff",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "8px",
zIndex: 1000, // ✅ 항상 최상위 UI로 유지됨
}}
>
{children}
<button onClick={onClose} style={{ marginLeft: "10px" }}>
닫기
</button>
</div>,
document.body // ✅ body 아래에 직접 렌더링되므로 부모 영향 없음!
);
};
export default PortalModal;


결론
React portal을 사용하면, overflow나 z-index에 영향을 받지 않는 UI 구현이 가능하다.
Portal 없이 position: fixed만 사용하면 뭐가 다를까?
근데 portal 없이 position:fixed만 사용해도 되지 않을까? 라는 의문이 들었다. 물론 여전히 부모 요소 안에 위치하기는 하겠지만, UI가 가려지지는 않을 것이라고 생각했다. 하지만, 부모의 z-index가 높으면 modal이 여전히 가려질 수 있다. 또한, fixed는 기본적으로 viewport를 기준으로 동작하지만, 부모가 transform, perspective, filter 속성을 가지면 부모 기준으로 움직이는 예외적인 경우가 발생할 수 있다고 한다.
'React' 카테고리의 다른 글
| react-intersection-observer를 활용한 이미지 최적화 (0) | 2025.03.04 |
|---|---|
| React query 적용 구조적 패턴 (0) | 2025.02.24 |
| 합성 컴포넌트 적용하기 (0) | 2025.02.11 |
| Error Boundary란 무엇일까? (0) | 2025.02.06 |
| useCallback() 적용하기 (0) | 2025.02.05 |