minstudio

고급 컴포넌트 패턴 (Portals & forwardRef)

UI의 제약을 넘어서는 고급 렌더링 패턴

부모 컴포넌트의 DOM 계층 구조를 벗어나 렌더링하는 createPortal을 활용한 모달(Modal) 구현법과, 부모 컴포넌트가 자식 커스텀 컴포넌트 내부의 DOM 요소에 직접 접근할 수 있게 해주는 forwardRef의 활용법을 다룹니다.

🚪 createPortal <div id="root"> (일반 컴포넌트) <div id="modal-root"> (포탈) z-index 충돌 방지를 위해 최상단에 렌더링 🎯 forwardRef <Parent /> (Ref 생성) <CustomInput /> (Ref 수신) 부모가 자식의 실제 DOM(input 등)을 직접 제어
🚪 createPortal: z-index 지옥 탈출
모달이나 툴팁을 렌더링할 때, 부모 컨테이너의 overflow: hidden이나 z-index에 막혀 화면에 제대로 표시되지 않는 경우가 많습니다. createPortal을 사용하면 논리적인 React 트리 구조는 유지하면서, 실제 DOM은 최상단 <body> 직속으로 빼내어 렌더링하므로 이러한 CSS 충돌을 완벽히 해결할 수 있습니다.
🎯 forwardRef: 컴포넌트를 관통하는 참조
부모 컴포넌트에서 커스텀 입력창 컴포넌트에 ref를 전달하고 싶을 때, 기본적으로 커스텀 컴포넌트는 ref를 받지 못합니다. forwardRef로 자식 컴포넌트를 감싸주면 부모가 넘긴 ref를 내부의 실제 <input> 등의 DOM에 바로 꽂아넣어 포커스 제어 등 직접적인 조작이 가능해집니다.
import React, { useRef } from 'react';
import CustomInput from './CustomInput';
import Modal from './Modal';

export default function App() {
  const inputRef = useRef(null);

  const focusInput = () => {
    // 부모 컴포넌트에서 자식 컴포넌트 내부의 DOM 함수(focus)를 직접 호출
    inputRef.current?.focus();
  };

  return (
    <div style={{ padding: '20px', background: '#f8fafc', borderRadius: '8px' }}>
      <CustomInput ref={inputRef} label="사용자 이름" />
      <button 
        onClick={focusInput} 
        style={{ marginTop: '16px', background: '#3b82f6', color: 'white', padding: '8px 16px', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
        포커스 이동하기
      </button>
      
      <Modal isOpen={true}>
        <h2 style={{marginTop: 0}}>이것은 포탈로 열린 모달입니다!</h2>
      </Modal>
    </div>
  );
}
실행 결과
고급 컴포넌트 패턴 (Portals & forwardRef) | Minstudio