[react] createPortal (모달 만들기)

dev stefanCho·2021년 12월 28일
0

react

목록 보기
15/19
post-custom-banner

createPortal을 써야하는 이유?

css의 stacking context에 의해서 z-index만으로는 Modal을 만들기에 충분하지 않습니다. z-index를 아무리 높여도 부모의 z-index가 낮다면 의도하지 않은 방향으로 랜더링이 될 것입니다.
예시 코드로 확인해봅시다.

import "./styles.css";

const One = {
  position: "absolute",
  zIndex: 1,
  width: 150,
  height: 150,
  background: "red"
};

const Two = {
  position: "absolute",
  zIndex: 2,
  width: 100,
  height: 100,
  background: "blue"
};

const Hundread = {
  position: "absolute",
  zIndex: 100,
  width: 300,
  height: 300,
  background: "purple"
};

export default function App() {
  return (
    <div className="App">
      <div style={One}>
        <div style={Hundread}>Will render Modal here...</div>
      </div>
      <div style={Two}>I am Two</div>
    </div>
  );
}

위와 같은 상황에서 Hundread안에 Modal을 띄우게 된다해도, I am Two가 더 상위에 뜨게 될 것입니다. (Modal 부모의 z-index가 낮기 때문입니다.)

따라서 index.html의 최상의 div를 하나 생성해서 거기에 Modal을 띄우는 방식을 취해야합니다. createPortal을 사용해서 예제를 만들어 봅니다.

모달 만들기 (Example)

아래 코드는 여기서 테스트해볼 수 있습니다.

index.html

public/index.html에서 root아래(혹은 위)에 portal용 div를 만듭니다.

    <div id="root"></div>
    <div id="portal"></div>

App.js

button을 클릭하여 isOpen을 true로 만들면, Modal이 열립니다.

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

export default function App() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <button onClick={() => setIsOpen(true)}>Modal</button>
      <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} />
    </div>
  );
}

Modal.js

Modal은 createPortal로 만듭니다. (react-dom)
createPortal의 2번째 파라미터값으로는 index.html에서 만든 div를 넣습니다.

import ReactDom from "react-dom";

const OVERLAY_STYLES = {
  position: "fixed",
  top: 0,
  left: 0,
  right: 0,
  bottom: 0,
  backgroundColor: "rgba(0,0,0,.7)",
  zIndex: 1000
};

const MODAL_STYLES = {
  position: "fixed",
  top: "50%",
  left: "50%",
  transform: "translate(-50%, -50%",
  padding: "50px",
  zIndex: 1000,
  backgroundColor: "#fff"
};

export const Modal = ({ onClose, isOpen }) => {
  if (!isOpen) {
    return null;
  }

  return ReactDom.createPortal(
    <div>
      <div style={OVERLAY_STYLES}></div>
      <div style={MODAL_STYLES}>
        <h1>Modal...</h1>
        <button onClick={onClose}>Close</button>
      </div>
    </div>,
    document.querySelector("#portal")
  );
};

Render와의 차이점

createPortal과 render는 컴포넌트를 특정 element에 아래에 랜더링 한다는 점이 같습니다.
차이점은 render는 최상위 컴포넌트로 새로 생성되는 것이라, 부모 컴포넌트의 event bubbling이 되지 않습니다. createPortal은 하위 컴포넌트로 생성되는 것이기 때문에, 부모 컴포넌트의 event bubbling이 가능합니다.

codesandbox 예시로 테스트해보면, createPortal은 상위 컴포넌트의 onClick이 실행됨을 확인할 수 있습니다.

Nextjs에서는 어떻게 사용할까?

nextjs에서는 public에 index.html파일이 없습니다.
_document.js를 만들어서 div를 하나 추가해주면 됩니다.

profile
Front-end Developer
post-custom-banner

0개의 댓글