React의 Portal은 특정 컴포넌트를 부모 컴포넌트의 DOM 계층 구조가 아니라, 지정된 DOM 노드에 렌더링할 수 있도록 도와주는 기능이다. 기본적으로 React는 컴포넌트 트리 구조를 따라 렌더링되지만, Portal을 사용하면 부모 컴포넌트의 DOM 구조를 벗어나 원하는 위치에 컴포넌트를 마운트할 수 있다.
-> Portal은 특정 UI 요소를 부모 컴포넌트의 DOM 구조를 벗어나 별도의 DOM 요소에 렌더링하고 싶을 때 유용하다. 때문에 모달, 드롭다운, 툴팁과 같이 부모 요소의 스타일이나 z-index 속성의 영향을 받지 않아야 하는 UI 컴포넌트에 권장된다.
우선 완성된 코드를 먼저 봐보자!
Modal.jsx
import React from "react";
import { createPortal } from "react-dom";
const Modal = ({ isOpen, onClose, children }) => {
if (!isOpen) return null; // 모달이 닫혀 있으면 렌더링하지 않음
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
<button onClick={onClose}>닫기</button>
</div>
</div>,
document.getElementById("modal-root") // 특정 DOM 요소에 렌더링
);
};
export default Modal;
App.jsx
import React, { useState } from "react";
import Modal from "./Modal";
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div>
<h1>React Portal 모달 예제</h1>
<button onClick={() => setIsModalOpen(true)}>모달 열기</button>
<Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
<h2>안녕하세요!</h2>
<p>이것은 Portal을 사용한 모달입니다.</p>
</Modal>
</div>
);
};
export default App;
index.html
<body>
<div id="root"></div>
<div id="modal-root"></div> <!-- Portal을 위한 추가적인 DOM 노드 -->
</body>
import React from "react";
import { createPortal } from "react-dom";
createPortal의 역할은 이 컴포넌트에 렌더링이 될 HTML 코드를 DOM내에 다른곳으로 옮기는 것이다.
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
<button onClick={onClose}>닫기</button>
</div>
</div>
);
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
<button onClick={onClose}>닫기</button>
</div>
</div>,
document.getElementById("modal-root") // 특정 DOM 요소에 렌더링
);
위 코드는 메인파일이나 인덱스 파일에서 보던것과 비슷하다!
main.jsximport { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import App from "./App.jsx"; createRoot(document.getElementById("root")).render( <StrictMode> <App /> </StrictMode> );
여기에 createRoot 메소드가 있는데 이것 또한 HTML파일의 요소가 필요하며, 여기에 리액트 앱이 렌더링된다. 이와 같이 createPortal을 사용함으로써 똑같은 리액트 앱의 출력 위치를 다르게 설정한다.
<body>
<div id="root"></div>
<div id="modal-root"></div> <!-- Portal을 위한 추가적인 DOM 노드 -->
</body>
const [isModalOpen, setIsModalOpen] = useState(false);
<button onClick={() => setIsModalOpen(true)}>모달 열기</button>
<Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
프로젝트 진행중에 모달을 여러개 사용해야 할 일이 있었다. 구현과정에서 조금씩 복잡해지고 css적용에 문제가 생겼다... 그러다가 검색을 통해 Portal을 알게 되었다! 아니 이런 대단한 녀석이 있을줄이야.... 이미 만들어놓은 컴포넌트에 Portal을 나중에 적용하려니까 일이 두배가 되어버렸어! 이래서 지식이 중요하구나 싶었다... 열심히해서 두번 일하지 않는 chill한 개발자가 되어보자!
(이런.. chill chill치 못하게 두 번 일해버렸네요....)