[react] React Portal?

2024. 10. 9. 22:44카테고리 없음

 

포탈..?

 

 

포탈하면 메이플 포탈만 생각나는데..

 

저번 수업에서 페이지 url에 변화가 없는데 새로운 페이지가 나오는 경우 이럴때 보통 포탈을 사용한다고 배웠었다.

그래서 포탈이 대체 무엇일까!하고 알아보게 되었다.

 

1. React Portal 개념

react 공식문서에 따르면 React Portal이란, 부모 컴포넌트의 Dom 계층 구조 바깥에 있는 Dom 노드로 자식을 렌더링하는 최고의 방법을 제공한다고 한다.

리액트의 데이터 흐름은 단방향(부모->자식)인데, portal을 사용하면 상태를 공유받되 자식 요소에서 상위로 UI를 렌더할 수 있다.

일반적으로 생각해보면 어떻게 자식요소에서 부모요소에 UI를 나오게 할 수 있을까..?라는 의문이 드는데,이를 portal이 해결해준다! 

즉, 자식 컴포넌트를 부모 컴포넌트 바깥에 있는 다른 컴포넌트에 전달할 수 있다는 뜻이다! 

 

렌더링 과정에서 tree구조에 따라 부모가 렌더링 되면 자식도 같이 렌더링이 일어나면서 불필요한 렌더링이 일어날 수 있다.

또한, z-index 처리로 부모 컴포넌트로부터 우선순위를 설정해야하는 경우도 있을 것이다. 부모의 JSX 요소에 영향을 받아 스타일링 시에 문제가 생길 수도 있기에, 이럴때 Portal을 사용해 독립적인 구조와 부모,자식 관계를 유지할 수 있는 것이다!

 

 

 

 

 

 

이렇게 주로 모달은 페이지의 맨 위에 위치해있어 '오버레이'를 하게되는데, css만으로도 모달을 구현할 수 있지만 좋은 구조가 아니라고 한다. 따라서 포탈은 보통 Modal, Dialog, Tooltip(요소에 마우스오버하면 추가적인 정보가 나타나게 하는 효과) 등을 띄우기 위해 많이 사용된다.

 

 

2. 사용 방법

   1]  모달 위치 정하기

 

return(
  <body>
    <div id="root"></div>
    <div id="modal"></div>
  </body>
)

 root가 가장 최상단 요소이고 그 아래로 modal 요소가 있다.

 

2] createPortal 호출

 

//ModalPortal.tsx

import React, { useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';

export const ModalPortal = ({ children }) => {
  const modalRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    modalRef.current = document.getElementById('modal');
  }, []); // 컴포넌트가 마운트될 때 한 번만 실행, useEffect 함수를 이용해 컴포넌트가 생기고, 사라질 때를 제어할 수 있다.

  // modalRef가 설정된 후에만 포털을 렌더링
  if (!modalRef.current) return null;

  return ReactDOM.createPortal(children, modalRef.current);
  //children: ModalPortal 컴포넌트 내부에 위치한 모든 JSX 코드
  // modalRef.current : id="modal"을 가진 요소를 참조
};

 

ReactDOM.createPortal(children, modalRef.current);

: 첫번째 인자에는 children으로 modal 요소를 넣어준다.

  두번째 인자에는 ref로 연결한 해당 dom 요소를 넣어줌으로써 연결한다.

 

//모달컴포넌트 생성

export const Modal = () => {
  return (
    <div className="Modal">
        <div className="Modal-content">
          <h3>모달 타이틀</h3>
          <p>모달 텍스트 </p>
          <button>닫기</button>
        </div>
      </div>
    </div>
  );
};

 

 

//App컴포넌트

import { Modal } from "./Modal";

const App=()=>{
  const [isOpen,setIsOpen]=useState(false);
  const modalRef=useRef<HTMLDIVElement | null>(null);
  
  const openModal=()=> setIsModalOpen(true);
  const closeModal=()=> setIsModalOpen(false);
  
  return(
  <div>
      <div id="root">
       <h3>모달일까?</h3>
      <button onClick={openModal}>열기</button>

      {/* modalRef를 통해 modal 요소에 접근 */}
      <div ref={modalRef} id="modal" />
      
      {/* 모달이 열릴 때만 렌더링 */}
      {isModalOpen && modalRef.current && (
        <Modal onClose={closeModal} />
      )}
    </div>
    </div>
  );
  }

 

이렇게 하면 모달을 생성할 수 있다!.

 

포탈을 사용하지 않은 모달

 

하지만 위처럼 포탈을 사용하기전에는 부모의 스타일링의 영향을 받아 모달컴포넌트에서도 해당 글자가 빨간색이 나오는 것이다..

 

 

 <ModalPortal>
       <Modal onClose={closeModal} />
</ModalPortal>

포탈을 사용한 모달

하지만 우리가 만들어둔 ModalPortal을 modal컴포넌트에 감싸주면 

이렇게 부모 컴포넌트의 스타일링을 벗어나 독립적으로 스타일링이 가능하다!

 

부모 컴포넌트의 영향을 받지 않도록, 최상위 계층으로 옮김으로써 의도치 않은 CSS 상의 방해를 받지않는 다는 장점이 있고

모달,툴팁처럼 튀어나오게 표현해야하는 요소뿐만 아니라 공통 레이아웃을 구성할때도 포탈을 사용함으로써 재사용성을 높일 수 있다!!