ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Portals
    리액트 2021. 9. 8. 17:00

    Portals

    Portals란?

    ReactDOM.createPortal(child, container)

    createPortal 메서드를 사용하면 자신의 부모 컴포넌트 DOM 계층구조 밖을 넘어선 어느 DOM 노드에 자식으로 렌더링 할 수 있습니다. 이름 그대로 포탈처럼 child 매개변수에 작성한 엘리먼트를 원하는 DOM 노드의 자식으로 넣을 수 있습니다.

     

    첫번째 인자(child)에는 엘리먼트, 문자열, fragment 등 리액트 자식을 작성할 수 있습니다.

    두번째 인자(container)에는 자식으로 렌더링하고자 하는 DOM 엘리먼트를 작성하시면 됩니다.

     

    이벤트 버블링과 Portals 사용예시

    portal은 일반적인 React 자식과 같이 동작을 합니다. portal은 DOM 트리에서는 위치가 바뀌어도 React 트리에서는 그대로 존재하기 때문에 context와 같은 기능을 사용할 수 있습니다.

     

    그럼 어떻게 원하는 DOM 트리에 렌더링을 하는 것일까요?

     

    이것은 이벤트 버블링과 관련되어 있습니다. portal 내부에 발생한 이벤트는 리액트 트리를 통해 상위 트리로 올라가 원하는 곳에 이벤트를 발생합니다. 

     

    Portals 사용예시

     

    원하는 상위 DOM트리에 렌더링할 수 있다는 특성을 활용하여 Portals는 주로 모달을 만들 때 사용합니다. 이번 예시는 Portals를 통해 모달을 생성해보겠습니다.

     

    public/index.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Portal</title>
      </head>
      <body>
        <div id="root"></div>
        <div id="modal-root"></div>
      </body>
    </html>

    modal-root를 추가하여 모달을 생성시 DOM 트리내에서 modal-root의 child로 나타나도록 하겠습니다. 

     

    src/App.js

    import React, { useState } from "react";
    import Modal from "./Modal";
    
    const ButtonStyle = {
      position: "relative",
      zIndex: 10,
    };
    
    const App = () => {
      const [isOpen, setIsOpen] = useState(false);
    
      // 모달 열기
      const openModal = () => {
        setIsOpen(true);
      };
      // 모달 닫기
      const closeModal = () => {
        setIsOpen(false);
      };
    
      return (
        <>
          <header>
            <button style={ButtonStyle} onClick={openModal}>
              모달 열기
            </button>
            <Modal isOpen={isOpen} closeModal={closeModal}>
              모달
            </Modal>
          </header>
          <div>콘텐츠</div>
        </>
      );
    };
    
    export default App;

    jsx부터 보겠습니다.

    모달을 로그인할 때 혹은 회원가입할 때 등으로 사용한다는 가정하에 header에 버튼을 작성했습니다. 그리고 그 아래에는 메인 콘텐츠라고 가정하겠습니다. 

     

    버튼을 누르면 isOpen의 state를 true로 하여 모달이 나타나도록 했습니다. 

     

    .src/Modal.js

    import React from "react";
    import { createPortal } from "react-dom";
    
    const ModalContainerStyle = {
      width: "100%",
      height: "100%",
      display: "flex",
      justifyContent: "center",
      alignItems: "center",
      position: "fixed",
      top: 0,
      left: 0,
      zIndex: 11,
    };
    
    const ModalBackgroundStyle = {
      position: "absolute",
      width: "100%",
      height: "100%",
      backgroundColor: "rgba(0, 0, 0, 0.75)",
      zIndex: 11,
    };
    
    const ChildrenStyle = {
      width: "300px",
      padding: "32px",
      backgroundColor: "#fff",
      zIndex: 11,
    };
    
    const Modal = ({ isOpen, closeModal, children }) => {
      if (!isOpen) return null;
    
      return createPortal(
        <div style={ModalContainerStyle}>
          <div style={ModalBackgroundStyle} onClick={closeModal} />
          <div style={ChildrenStyle}>
            {children}
            <button onClick={closeModal}>모달 닫기</button>
          </div>
        </div>,
        document.getElementById("modal-root")
      );
    };
    
    export default Modal;

    Modal 컴포넌트는 isOpen이 true일 때 createPortal() 메서드로 생성한 jsx를 반환합니다. 보시다시피 createPortal의 첫번째 인자에는 리액트 자식을 작성했으며, 두번째 인자에는 modal-root의 DOM child로 넣을 것으로 했습니다. 

     

    사용자가 모달에 집중을 할 수 있도록 모달 바탕색을 넣어 기존 콘텐츠로부터 분리할 수 있도록 했습니다.

     

    결과물

     

    모달이 잘 작동합니다.

     

    DOM을 보겠습니다

     

    원하는대로 modal-root에 모달이 생긴 것을 확인 할 수가 있습니다.

     

    생각할점

    • 로그인 모달, 회원가입 모달 등 모달을 재활용하여 쓸 경우도 많습니다. 이럴 경우 모달을 재사용할 수 있도록 만드는 것이 중요한데 어떻게 코드를 작성해야할지 고민해야겠습니다. 

     

    참고자료

     

    Portals – React

    A JavaScript library for building user interfaces

    ko.reactjs.org

     

    '리액트' 카테고리의 다른 글

    react-router-dom  (0) 2021.09.15
    ref & useRef() & forwardRef()  (0) 2021.09.11
    Context  (0) 2021.09.05
    Lists & Keys & Reconciliation  (0) 2021.08.29
    JSX & createElement() & reactDOM.render()  (0) 2021.08.24
Designed by Tistory.