코딩하는라민

[React] React Portal 사용하기 본문

Core/React

[React] React Portal 사용하기

코딩하는라민 2024. 4. 30. 17:00
728x90
반응형

[React] React Portal 사용하기

 

핀터레스트 : https://realtimevfx.com/t/simon-kratz-sketch-28/10815

 

Portal 이란?

리액트의 `Portal` 은 리액트 애플리케이션에서 DOM 트리다른 위치로 자식 요소를 렌더링하는 기능을 제공한다.

 

리액트는 body 태그 아래에 root 라는 id 를 가진 진입점에 가상 DOM 을 형성하고 실제로 모든 컴포넌트를 렌더링을 한다. Portal 은 새로운 진입점을 만들어서 부모 요소의 DOM 트리 구조와 별개의 위치에 컴포넌트를 렌더링하는 기능이다.

 

공식문서
Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공합니다.

 

Portal 을 사용해서 독립적인 위치에 컴포넌트를 렌더링하면 

  • 코드가 간결해져 유지보수성이 증가한다.
  • 부모의 스타일에 영향받지 않아 스타일링 충돌을 피할 수 있다.
  • 이벤트가 발생한 위치와 실제 이벤트가 처리되는 위치가 다르기 때문에 이벤트 버블링을 예방할 수 있다.
  • 독립적인 컴포넌트로 상위 계층으로부터 영향받지 않아 성능을 최적화할 수 있다.

 

Portal 은 어떤 경우에 사용할까

모달 or 툴팁

대표적으로 모달 혹은 툴팁과 같은 팝업 요소 렌더링하는 경우에 사용한다.

 

모달은 사용자에게 어떤 작업을 진행하기 전 확인 요청, 추가 정보를 입력하도록 유도하는 경우 사용한다.

툴팁은 추가적인 정보를 제공하고, 기능에 대해 설명할 때 사용한다.

 

이런 요소들은 다른 요소 위에 나타나거나 특정 위치에 표시되어야 하기 때문에 Portal 을 이용하면 이를 세밀하게 제어할 수 있다.

 

Portal 사용법

기본 문법

Portal 을 생성하는 방법은 `createPortal()` 을 이용하면 된다. createPortal 을 호출하여 JSX 를 domNode 로 전달한다.

createPortal(children, domNode, key?)
  • children : 사용하고자 하는 컴포넌트 혹은 문자열/숫자/배열 등 React 로 렌더링할 수 있는 요소들
  • domNode : chidlren 을 렌더링할 위치(모달 진입점)
  • key? : 선택 요소로 고유 문자열 혹은 번호

 

1. index.html 에 새로운 진입점 만들기

Portal 은 이미 존재하는 Node 에 렌더링이 가능하기 때문에 기존 진입점 외의 새로운 진입점을 생성해준다.

<!doctype html>
<html lang="en">
  <head>
    // ...
  </head>
  <body>
    <div id="root"></div>
    <div id="modal-portal"></div> // ✅ 새로운 진입점
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

 

2. createPortal( )

공식 문서에는 다음과 같이 사용하고 있다.

import { createPortal } from 'react-dom';

function MyComponent() {
  return (
    <div style={{ border: '2px solid black' }}>
      <p>This child is placed in the parent div.</p>
      {createPortal(
        <p>This child is placed in the document body.</p>,
        document.body
      )}
    </div>
  );
}

 

위의 첫번째 p 태그는 root 안에 렌더링되고, 두 번째 createPortal 을 이용해 추가한 p 태그는 root 가 아닌 body 아래에 위치한다.

<body>
  <div id="root">
    ...
      <div style="border: 2px solid black">
        <p>This child is placed inside the parent div.</p>
      </div>
    ...
  </div>
  <p>This child is placed in the document body.</p>
</body>

 

하지만

컴포넌트에 직접 createPortal 을 사용하면 재사용이 불가능하기 때문에 재사용 가능한 portal 로 만들어보자.

// src/components/modal/modalPortal.tsx
import { ReactNode } from 'react'
import { createPortal } from 'react-dom'

const ModalPortal = ({ children }: { children: ReactNode }) => {
  const modalRoot = document.getElementById('modal-portal') as HTMLElement
  return createPortal(children, modalRoot)
}

 

3. 사용할 모달 컴포넌트 생성

// src/components/modal/Modal.tsx
const Modal = ({
  close,
}: {
  close: () => void
}) => {
  return (
    <Overlay onClick={close}>
      <Container
        onClick={e => e.stopPropagation()}
      >
        모달창입니다.
      </Container>
    </Overlay>
  )
}

 

4. Portal 내부에 사용할 컴포넌트 감싸기

// src/page/Home.tsx
const Home = () => {
  const [isOpen, setIsOpen] = useState(false);

  const open = () => setIsOpen(true);
  const close = () => setIsOpen(false);
 
  return (
    <Container>
      <Button onClick={open}>
        열기
      </Button>
      {isOpen && (
        <ModalPortal>
          <Modal close={close} />
        </ModalPortal>
      )}
    </Container>
  )
}

 

Portal 사용 전후 비교

사용 전

Portal 사용 전에는 모달 컴포넌트가 메인 DOM 트리의 `root` 에 위치

 

사용 후

Portal 사용 후 모달 컴포넌트가 메인 DOM 트리와는 별개의 개별적인 DOM Node인 `modal-portal` 에 위치

 


참고

 

 

728x90
반응형