본문 바로가기

Web Development/Next.js

React Portal 정리

1. 무엇인가?

React Portal은 React 컴포넌트를 기존 부모 DOM 계층이 아닌, DOM 트리의 임의의 위치(예: 루트나 특정 엘리먼트)에 렌더링할 수 있게 해주는 기능입니다.

즉, 계층 구조와 무관하게 원하는 위치에 컴포넌트를 그릴 수 있습니다.


2. 왜 사용하는가?

Portal은 부모 DOM 계층의 스타일/레이아웃/기능 한계를 극복하기 위해 사용합니다.

  • 레이아웃/스타일링 이슈 해결(예: 모달, 드롭다운, 툴팁 등)
  • → 부모 요소의 overflow: hidden, z-index, position: relative 등 스타일 속성 영향을 받지 않고, 원하는 레이어(예: 화면 맨 위)에 노출 가능
  • 접근성 개선
  • → 모달 등을 DOM 트리 최상단에 렌더링함으로써, 스크린리더나 키보드 네비게이션 등 보조기기가 올바르게 포커스를 감지
  • 글로벌 이벤트/UI 독립성→ 여러 부모 계층에 얽매이지 않으므로, 컴포넌트 재사용성과 이동성도 높아짐
  • → ESC 키로 닫기, 스크롤 락 등 전역 이벤트가 필요한 UI에 유리

3. 어떻게 사용하는가?

기본 문법

import { createPortal } from 'react-dom';

createPortal(child, container)
  • child: 렌더링할 React 노드(JSX)
  • container: 실제로 삽입할 DOM 노드 (예: document.body, document.getElementById('modal-root'))

예시 코드

import { createPortal } from 'react-dom';

function Modal({ children, onClose }) {
  return createPortal(
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    document.body
  );
}

4. Portal의 작동 원리

구분 설명
물리적 DOM Portal로 렌더링한 컴포넌트는 지정한 container(DOM 노드)에 실제로 추가됨
React 트리 논리적으로는 기존 부모 컴포넌트의 자식이므로, Context, 이벤트 버블링 등에 영향 없음
이벤트 버블링 React 합성 이벤트는 DOM 위치와 관계없이 React 트리 기준으로 버블링
Context Portal 내부에서도 부모 Context를 그대로 사용할 수 있음

5. Portal 주요 사용 사례

  • 모달/다이얼로그: z-index, overflow, 포커스 관리 이슈 해결
  • 드롭다운/툴팁/컨텍스트 메뉴: overflow: hidden 등에 잘리지 않도록 처리
  • 토스트/글로벌 알림: 언제 어디서든 UI 위에 자연스럽게 띄우기

6. 실전 팁 및 주의사항

1. 모달 전용 컨테이너(div) 사용

  • 여러 모달이 중첩될 수 있으면 <div id="modal-root"></div> 등 별도 컨테이너에 모아 렌더링하는 것이 좋음
    • 스타일, z-index, 애니메이션 관리가 쉬움
    • “마지막 띄운 모달이 제일 위”를 자연스럽게 보장
    • 포커스 트랩, 스크린리더 안내 등 전역 처리가 쉬움
    • 여러 div에 분산하면 z-index 충돌 등 관리가 어려움

2. 접근성(a11y) 보완을 위한 추가 구현 필요

  • 포커스 트랩(Focus Trap)
    • 모달 내에서만 탭키 포커스가 이동하도록 제한하는 기능
    • 모달이 열릴 때,
      1. 첫 번째 focusable 요소로 포커스 이동
      2. Tab키를 누르면 마지막에서 다시 처음으로 이동
      3. 배경으로 포커스가 빠지지 않게 해야 함
- 접근성(a11y)에서 필수, 라이브러리(focus-trap-react 등) 사용 권장
  • 스크롤 락(Scroll Lock)
    • 모달, 오버레이가 떠 있는 동안 body의 스크롤을 막는 기능
    • 모달이 열릴 때:
document.body.style.overflow = 'hidden';
      • 모달이 닫힐 때:
document.body.style.overflow = '';

7. 정리

  • Portal은 React 트리와 물리적 DOM 구조를 분리하여, 스타일/접근성/UI 요구를 모두 만족시키는 실전 필수 기술
  • 단순 렌더링만으로는 부족하며, 포커스 트랩/스크롤 락/접근성 보완 등 추가 구현이 반드시 필요
  • 여러 포탈 UI가 있을 땐, 전용 컨테이너(div)를 적극 활용해서 관리의 일관성을 높일 것