React의 Portals

Suyo·2025년 2월 2일
0

Portal이란?

React의 Portal은 특정 컴포넌트를 부모 컴포넌트의 DOM 계층 구조가 아니라, 지정된 DOM 노드에 렌더링할 수 있도록 도와주는 기능이다. 기본적으로 React는 컴포넌트 트리 구조를 따라 렌더링되지만, Portal을 사용하면 부모 컴포넌트의 DOM 구조를 벗어나 원하는 위치에 컴포넌트를 마운트할 수 있다.


Portals를 사용하는 이유

  1. DOM 계층 구조를 벗어나 렌더링
  • React의 컴포넌트 트리와 관계없이 특정한 DOM 요소에 렌더링할 수 있다.
  • 예를 들어, 모달, 드롭다운, 툴팁 같은 UI 요소를 body 태그 바로 아래에 렌더링하여 스타일과 동작을 안정적으로 유지할 수 있다.
  1. CSS z-index 문제 해결
  • 부모 요소의 overflow: hidden 또는 z-index 속성으로 인해 모달이나 드롭다운이 잘리는 문제를 방지할 수 있다.
  1. 이벤트 버블링의 개선
  • Portal을 사용해도 이벤트는 React의 이벤트 시스템을 그대로 따르지만, 부모 컴포넌트와 겹치는 이벤트(예: onClick 이벤트로 모달이 닫히는 문제)를 효과적으로 관리할 수 있다.

Portal이 권장되는 상황

  1. 모달(Modal)
  • 모달이 부모 컴포넌트 내에서 렌더링되면 overflow: hidden 같은 스타일에 의해 가려질 수 있다.
  • Portal을 사용하여 document.body 아래에 렌더링하면 문제를 방지할 수 있다.
  1. 드롭다운 메뉴 (Dropdown Menu)
  • 부모 요소의 overflow: hidden 때문에 드롭다운이 잘리는 문제를 해결할 수 있다.
  1. 툴팁 (Tooltip)
  • 특정 요소에 툴팁을 표시할 때 부모 요소의 overflow 속성에 영향을 받지 않도록 별도의 DOM 요소에 렌더링한다.

-> Portal은 특정 UI 요소를 부모 컴포넌트의 DOM 구조를 벗어나 별도의 DOM 요소에 렌더링하고 싶을 때 유용하다. 때문에 모달, 드롭다운, 툴팁과 같이 부모 요소의 스타일이나 z-index 속성의 영향을 받지 않아야 하는 UI 컴포넌트에 권장된다.


Portal 사용법

우선 완성된 코드를 먼저 봐보자!

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>

Modal.jsx - Portal을 통한 모달 렌더링

  1. 우선 react-dom에서 createPortal을 import한다.
import React from "react";
import { createPortal } from "react-dom";

createPortal의 역할은 이 컴포넌트에 렌더링이 될 HTML 코드를 DOM내에 다른곳으로 옮기는 것이다.

  1. 그렇게 하기 위해 전체 코드를 감싸주고 createPortal을 이용하여 보내준다. createPortal은 두가지 인수를 받는데 첫번째 인수는 JSX코드이며, 두번째 인수는 HTML요소이다.
return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        {children}
        <button onClick={onClose}>닫기</button>
      </div>
    </div>
);
  1. 두번째 인수는 기본 브라우저 API로 선택해야한다. document.getElementById를 사용하면 된다.
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.jsx

import { 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을 사용함으로써 똑같은 리액트 앱의 출력 위치를 다르게 설정한다.

index.html - Portal을 위한 DOM 요소 추가

  1. Portal을 위한 추가적인 DOM 노드를 추가하자.
<body>
  <div id="root"></div>
  <div id="modal-root"></div> <!-- Portal을 위한 추가적인 DOM 노드 -->
</body>

App.js - 모달 상태 관리

  1. useState를 사용하여 모달 열림/닫힘 상태를 관리한다.
const [isModalOpen, setIsModalOpen] = useState(false);
  1. 버튼을 클릭하면 isModalOpen을 true로 설정하여 모달을 연다.
<button onClick={() => setIsModalOpen(true)}>모달 열기</button>
  1. isOpen props를 통해 모달을 열지 결정하고,
    onClose를 호출하면 setIsModalOpen(false)가 실행되어 모달이 닫힌다.
<Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>

프로젝트 진행중에 모달을 여러개 사용해야 할 일이 있었다. 구현과정에서 조금씩 복잡해지고 css적용에 문제가 생겼다... 그러다가 검색을 통해 Portal을 알게 되었다! 아니 이런 대단한 녀석이 있을줄이야.... 이미 만들어놓은 컴포넌트에 Portal을 나중에 적용하려니까 일이 두배가 되어버렸어! 이래서 지식이 중요하구나 싶었다... 열심히해서 두번 일하지 않는 chill한 개발자가 되어보자!
(이런.. chill chill치 못하게 두 번 일해버렸네요....)

profile
Mee-

0개의 댓글