React에서 모달 구현하기: Context API와 Portal 이해하기

oversleep·2025년 4월 10일

web-development

목록 보기
7/23

React로 모달을 구현할 때 사용되는 핵심 개념들에 대해 자세히 알아보겠습니다.
모달 컴포넌트를 만들 때 createContext, useContext, createPortal 같은 API들이 왜 필요하고 어떻게 작동하는지 알아보겠습니다.

1. React Context API: 데이터를 깊숙이 전달하기

createContext

createContext는 컴포넌트 트리 전체에서 데이터를 공유할 수 있게 해주는 React API입니다.

const ModalContext = createContext({
  isOpen: false,
  onClose: () => {},
});

이 코드는 모달의 상태(isOpen)와 닫는 함수(onClose)를 담는 컨텍스트를 생성합니다.
기본값은 모달이 닫혀있고, onClose 함수는 아무 동작도 하지 않는 빈 함수입니다.

ModalContext.Provider

Provider는 Context의 값을 자식 컴포넌트들에게 제공합니다.

<ModalContext.Provider value={{ isOpen, onClose }}>
  {children}
</ModalContext.Provider>

이렇게 하면 Provider 내부의 모든 컴포넌트가 isOpenonClose 값에 접근할 수 있게 됩니다.

useContext

자식 컴포넌트에서 Context 값을 사용하려면 useContext 훅을 사용합니다.

const { onClose } = useContext(ModalContext);

이 코드는 ModalContext에서 onClose 함수를 가져옵니다. 이렇게 하면 props drilling(여러 계층을 통해 props를 전달하는 것) 없이도 필요한 데이터에 접근할 수 있습니다.

2. React Portal: DOM 계층 구조를 벗어나기

createPortal

모달은 보통 페이지의 최상위에 렌더링되어야 합니다.
createPortal을 사용하면 컴포넌트를 DOM 트리의 다른 위치에 렌더링할 수 있습니다.

return createPortal(
  <div className="modal-overlay">
    <div className="modal-content">
      {children}
    </div>
  </div>,
  document.body
);

이 코드는 모달 내용을 현재 컴포넌트 위치가 아닌, document.body 바로 아래에 렌더링합니다. 이렇게 하면:

  1. 모달이 부모 컴포넌트의 스타일(overflow: hidden, z-index 등)에 영향을 받지 않습니다.
  2. 모달이 항상 페이지의 최상위에 표시됩니다.
  3. 스크린 리더와 같은 접근성 도구에도 더 적합합니다.

3. 모달 컴포넌트의 작동 방식

전체 흐름을 살펴보면:

  1. 모달 컴포넌트가 isOpenonClose props를 받습니다.
  2. isOpen이 true면 Portal을 사용해 모달 내용을 body에 렌더링합니다.
  3. Context Provider로 isOpenonClose 값을 자식 컴포넌트들에게 제공합니다.
  4. Header, Body, Footer 등의 자식 컴포넌트는 useContext로 이 값들에 접근합니다.
  5. 예를 들어, 닫기 버튼은 useContext로 가져온 onClose 함수를 호출해 모달을 닫습니다.

4. 컴파운드 컴포넌트 패턴 활용하기

이 모달은 컴파운드 컴포넌트 패턴을 사용합니다.
여러 관련 컴포넌트가 하나의 그룹으로 작동하면서도 개별적으로 조작할 수 있게 해주는 패턴입니다.

<Modal isOpen={isOpen} onClose={onClose}>
  <Modal.Header>제목</Modal.Header>
  <Modal.Body>내용</Modal.Body>
  <Modal.Footer>
    <Modal.CancelButton onClick={handleCancel} />
    <Modal.ConfirmButton onClick={handleConfirm}>확인</Modal.ConfirmButton>
  </Modal.Footer>
</Modal>

이 패턴의 장점은:

  • 사용하기 직관적입니다
  • 컴포넌트 구조가 명확하게 보입니다
  • 각 부분을 쉽게 커스터마이징할 수 있습니다

실제 사용 사례

모달이 필요한 여러 상황에서 이 컴포넌트를 활용할 수 있습니다:

// 확인 모달
<Modal isOpen={isConfirmOpen} onClose={closeConfirmModal}>
  <Modal.Header>확인</Modal.Header>
  <Modal.Body>변경사항을 저장하시겠습니까?</Modal.Body>
  <Modal.Footer>
    <Modal.CancelButton onClick={() => {}} />
    <Modal.ConfirmButton onClick={handleSave}>저장</Modal.ConfirmButton>
  </Modal.Footer>
</Modal>

// 삭제 모달
<Modal isOpen={isDeleteOpen} onClose={closeDeleteModal}>
  <Modal.Header>삭제 확인</Modal.Header>
  <Modal.Body>정말 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.</Modal.Body>
  <Modal.Footer>
    <Modal.CancelButton onClick={() => {}} />
    <Modal.ConfirmButton onClick={handleDelete} variant="danger">
      삭제
    </Modal.ConfirmButton>
  </Modal.Footer>
</Modal>

결론

React의 Context API와 Portal을 사용하면 유연하고 재사용 가능한 모달 컴포넌트를 만들 수 있습니다. 컴파운드 컴포넌트 패턴을 통해 사용하기 쉬우면서도 높은 커스터마이징이 가능한 컴포넌트를 구현할 수 있습니다.

이 패턴을 활용하면 모달뿐만 아니라 드롭다운, 탭, 아코디언 등 다양한 복합 컴포넌트도 효과적으로 구현할 수 있습니다.

profile
궁금한 것, 했던 것, 시행착오 그리고 기억하고 싶은 것들을 기록합니다.

0개의 댓글