Portal

8주차 과제로 모달을 만들어보기는 했지만 너무 미숙하게 만든 감이 있어서 새로 만들어 보고 싶었다.

이번에는 모달 만들때 주로 쓰는 방식이라는 portal 을 이용해보기로 했다.

portal 을 사용하는 이유

리액트는 부모 컴포넌트가 렌더링 되면 자식 컴포넌트가 렌더링 되는 Tree 구조를 가지고 있다. 

보통 최상위 컴포넌트인 #root 의 div 태그 아래에 부모 컴포넌트부터 렌더링 된다.

이런 Tree 구조는 DOM 계층 구조에서 부모-자식 간에 CSS 상속 같은 영향을 미친다. 

하지만 리액트 포탈을 사용하면 독립적인 위치에서 렌더링한다. 

독립적인 위치에서 렌더링 하면 부모 컴포넌트의 영향을 받지 않을 수 있다. 

따라서 리액트 포탈을 사용하면 CSS 충돌을 방지하고 레이아웃 및 이벤트 처리를 효과적으로 할 수 있어

유지 보수성을 향상시킬 수 있다.


모달 구현하기

기본적인 구현 방식은 아래와 같다.

  1. HTML 최상위에 #modal-container 를 둔다.
  2. 모달을 만든 후 portal 을 이용하여 #modal-container 를 연결한다.

index.html

<!doctype html>
<html lang="ko">
  <head>    
    <title>Open Mind</title>
  </head>
  <body>
    <div id="root"></div>
    <div id="modal-container"></div>
  </body>
</html>

App.jsx

function App() {
  const [ isModalOpen, setIsModalOpen ] = useState(false);
  const handleOpen = () => setIsModalOpen(true);
  const handleClose = () => setIsModalOpen(false);
  
  return (
    <>
      {isModalOpen && (
      <Modal onClose={handleClose}>
        모달 내용
        <button onClick={onClose}>모달 닫기</button>
      </Modal>
      )}
      <button onClick={handleOpen}>모달 열기</button>
    </>
  );
}

export default App;

Modal.jsx

import { useRef } from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";

function Modal(props) {
  const { children, onClose } = props;
  const modalRef = useRef(null);

  // useRef를 이용해 모달창 바깥을 누르면 모달창 꺼지도록 함
  const handleClickOutside = (e) => {
    if (modalRef.current && !modalRef.current.contains(e.target)) {
      onClose();
    }
  };

  return ReactDOM.createPortal(
    <ModalBg onClick={handleClickOutside}>
      <ModalContainer ref={modalRef}>{children}</ModalContainer>
    </ModalBg>,
    document.getElementById("modal-container"),
  );
}

export default Modal;

const ModalBg = styled.div`
  // 모달 배경 스타일
`;

const ModalContainer = styled.div`
  // 모달 컨테이너 스타일
`;

 

 

'프론트엔드 > React' 카테고리의 다른 글

react-query와 redux  (0) 2024.02.01
Vite 와 CRA  (0) 2024.01.20
Yarn Berry와 NPM  (0) 2024.01.19

+ Recent posts