react-create-portal 에 대하여...

HarryFront·2021년 3월 30일
5
post-thumbnail

react로 개발을 진행하다보니 react-create-portal 즉, 리액트 v16 에서 도입된 기능인 Portals를 접하게 되었다. 사실 나는 vue.js 개발자다보니 react가 조금 생소하긴 했지만 개발을 해나가는데에는 큰 어려움이 없었다. 하지만 Portals 기능은 조금 생소하였는데 써보게 되니 '굉장히 편리하구나! 유용하게 써먹을 수 있겠네!' 라는 생각이 들게 되었다.

Portals 가 대체 뭐야?!?!

리액트 v16에서 도입된 'Portals'는 컴포넌트를 랜더링 시킬 때 랜더링 시킬 DOM을 선택하여 부모 컴포넌트의 바깥에서도 렌더링 할 수 있게 해주는 기능이다.
이러한 기능을 써먹을 수 있는 가장 큰 예로는 'Modal'이나 'Popup'이다.
생각해보자! 공통된 모달창이나 팝업창을 지속적으로 띄워야 하는데 그 안에 들어갈 내용들은 페이지마다 다르다고 가정해보자.
원래는 모달 컴포넌트와 팝업 컴포넌트를 만들어 놓고 해당 페이지를 보여주는 상위 컴포넌트에서 모달 및 팝업 컴포넌트를 넣어주어야 한다. 사실.. Portals도 크게 다르지는 않다. 하지만 position 스타일 요소를 활용하여 모달이나 팝업을 띄워줄때 상위 노드들의 overflow 요소나 z-index에 대한 우선순위가 뒤로 밀릴 수 있어 작업을 하는데 방해가 될수도 있다.

이럴때!! index.html이나 App.js 같은 부분에 모달창이나 팝업창이 뜰 수 있는 Element가 이미 지정이 되어 있다면 위와 같은 부분들이 모두 손쉽게 해결될 수 있다.

누가 그러더라 개발자는 글이 아닌 소스로 이해 해야한다고... 그럼.. 만들어볼까?

./src/style.css

.App {
  font-family: sans-serif;
  text-align: center;
}

.modal-wrap {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 999;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  visibility: hidden;
  opacity: 0;
}

.modal-wrap.active {
  visibility: visible;
  opacity: 1;
  transition: opacity ease 0.25s;
}

.modal-wrap .overlay {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
}

.modal-wrap .modal-con {
  position: relative;
  z-index: 10;
  max-width: 500px;
  min-width: 300px;
  min-height: 100px;
  width: 33%;
  padding: 20px;
  border-radius: 5px;
  background: #fff;
  transform: translateY(80px);
  transition: transform ease 0.3s 0.1s;
}

.modal-wrap.active .modal-con {
  position: relative;
  z-index: 10;
  max-width: 500px;
  min-width: 300px;
  min-height: 100px;
  width: 33%;
  padding: 20px;
  border-radius: 5px;
  background: #fff;
  transform: translateY(0px);
}

.modal-wrap .modal-con .contents {
  padding: 20px 0;
}

.modal-wrap .modal-con .bottom {
  text-align: right;
  margin-top: 10px;
}

일단 모달이 작동될 수 있도록 css를 작성한 css 소스이다. 모달 창이 보여져야할때는 .modal-wrap에 active 클래스를 부여하고 active클래스가 있을 경우 visiblity는 visible, active클래스가 존재하지 않을 때는 hidden을 주어 모달창의 보여짐을 설정하고 animation을 위해 opacity와 tranform에 transition 효과를 주었다.

이제 modal 컴포넌트를 작성해보자

./src/Modal.js

export default function Modal() {
  return (
    <div className="modal-wrap">
      <div className="overlay"></div>
      <div className="modal-con">
        <div className="contents">모달이 열렸다!</div>
        <div className="bottom">
          <button type="button">
            모달 닫기
          </button>
        </div>
      </div>
    </div>
  );
}

그리고 Portals 기능을 써보기 위해 ./public/index.html과 Portals 기능 구현을 담당하기 위한 ModalPortals 컴포넌트를 만들어보자

./public/index.html

  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
    <div id="modal"></div>
  </body>

id를 modal로 하는 element를 만들고

./src/ModalPortals.js

import ReactDOM from "react-dom";

export default function ModalPortals({ children }) {
  const modalElement = document.querySelector("#modal");
  return ReactDOM.createPortal(children, modalElement);
  // createPortal(ModalPortals안에서 랜더링될 컴포넌트, 랜더링 시킬 상위 DOM Element)
}

id가 modal인 element를 선택하고 해당 element에 ModalPortals 컴포넌트 안에 랜더링될 컴포넌트를 createPortal 메소드의 첫번째 인자로 넣어준다. 여기까지 해주었다면 최상위 컴포넌트인 App.js를 해당 모달 기능이 잘 돌아갈 수 있게끔 소스코드를 작성해준다.

./src/App.js

import "./styles.css";
import { useState } from "react";
import ModalPortals from "./ModalPortals";
import Modal from "./Modal";

export default function App() {
  const [modal, setModal] = useState(false);

  const handleModalShow = (status) => {
    setModal(status);
  };

  return (
    <div className="App">
      <h1>쫑's의 Portals 모달 만들기</h1>
      <h2>밑에 모달 버튼을 눌러보세요!</h2>

      <div>
        <button
          type="button"
          onClick={() => {
            handleModalShow(true);
          }}
        >
          모달 버튼
        </button>
      </div>

      <ModalPortals>
        <Modal show={modal} handleModalShow={handleModalShow} />
      </ModalPortals>
    </div>
  );
}

useState Hook을 활용하여 modal이 활성화 되었는지 비활성화 되어 있는지를 구분할 수 있도록 해주고 handleModalShow()라는 Modal상태를 핸들링하는 이벤트 메소드를 만들어 setModal을 이용하여 modal state의 값을 변경할 수 있게 해준다. Modal 컴포넌트에는 modal state를 props로 전달하고 Modal상태를 핸들링하는 이벤트 메소드인 handleModalShow도 props로 전달한다.
이제는 App.js에서 props로 전달하는 state와 method를 Modal 컴포넌트에서 받을 수 있도록 Modal.js에서 설정을 해주도록 하자.

./src/Modal.js

export default function Modal({ show, handleModalShow }) {
  return (
    <div className={"modal-wrap " + (show ? "active" : "")}>
      <div
        className="overlay"
        onClick={() => {
          handleModalShow(false);
        }}
      ></div>
      <div className="modal-con">
        <div className="contents">모달이 열렸다!</div>
        <div className="bottom">
          <button
            type="button"
            onClick={() => {
              handleModalShow(false);
            }}
          >
            모달 닫기
          </button>
        </div>
      </div>
    </div>
  );
}

App.js에서 전달하는 props를 Modal.js에서 받을 수 있도록 설정해준 뒤 .overlay와 '모달 닫기' 버튼에 이벤트 메소드를 작동할 수 있도록 onClick 이벤트를 설정해준다.
이제 모든 기능 구현이 완료되었다. 한번 실행해보도록 하자.




index.html에서 id가 modal인 element 안에서 Modal 컴포넌트가 랜더링이 되고 있음을 확인 할 수 있으며 작동이 잘 되고 있음을 확인 할 수 있다. 이 처럼 부모 컴포넌트 바깥에서 Portals 기능을 활용하여 모달창을 구현할 수 있음을 확인할 수 있다.

결론

Portals 기능은 모든 페이지에서 공통적으로 자주 사용되는 모달창이나 팝업창, 알림창 등을 구현할 때 활용하기 좋은 기능인것 같다. 특히 커스터마이징된 alert나 confirm 기능을 구현할때 활용도가 높을 것으로 생각되며 알아두면 좋을 기능 중의 하나로 생각된다.

P.S Portals기능의 더 좋은 활용 사례를 알고계신다면 피드백 주시면 너무 감사할거 같아요! 혹시나 제가 잘못된 설명을 한게 있다면 그것 또한 피드백 감사히 받도록 하겠습니다 ^^

profile
기획자 같지만 개발자 같게 개발자 같지만 디자이너 같게

1개의 댓글

comment-user-thumbnail
2023년 8월 30일

다른 root를 가진 컴포넌트를 불러올 수 있는 기능이 Portal 이군요.
랜더링 시에 기존 한 곳에서 모든 것들을 처리하는 부담을 최소화 할 수 있기에 더욱 좋은 기능같네요. 감사합니다.

답글 달기