[TIL 221126] Portal

ponyo·2022년 11월 26일
0

Today I Learned

목록 보기
5/30
post-thumbnail

Portal ?

Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공합니다.

ReactDOM.createPortal(child, container)

첫 번째 인자(child)는 엘리먼트, 문자열 혹은 fragment와 같은 어떤 종류이든 렌더링할 수 있는 React 자식입니다. 두 번째 인자(container)는 DOM 엘리먼트입니다.

Portal 사용 이유

// Example.jsx

import React from 'react';
import ThankyouDialog from './ThankyouDialog';

export default function Example() {
  return (
    <div>
      <ThankyouDialog />
      <div style={{ position: "absolute"}}
      ><button>하하하</button></div>
    </div>
  )
}
// Dialog.jsx

import React, { useState } from 'react'

export default function Dialog(props) {
  const [isOpen, setIsOpen] = useState(false)
  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open</button>
      {isOpen && (<div
        style={{
          position: "absolute",
          zIndex: 99,
          top: "50%",
          left: "50%",
          transform: "translate(-50%,-50%)",
          border: "1px solid black",
          padding: 24,
          backgroundColor: "white",
        }}
      >
        {typeof props.title === "string" ? (<h1>{props.title}</h1>) : (props.title)}
        <h5>{props.description}</h5>
        <button style={{ backgroundColor: "red", color: "white" }} onClick={() => setIsOpen(false)}>{props.button}</button>
        </div>
        )}
        {isOpen && <div style={{
          position: 'fixed',
          top: 0,
          left: 0,
          right: 0,
          bottom: 0,
          backgroundColor: 'lightgray',
        }}/>}
    </>
  )
}
// ThankyouDialog.jsx

import React from 'react'
import Dialog from './Dialog'

export default function ThankyouDialog() {
  return (
    <Dialog title={<h1 style={{color: "gold"}}>Thanks</h1>} description="It is honor to meet you!" button="close"/>
  )
}

Dialog를 composition 한 ThankyouDialog는 인라인 속성으로 z-index 속성을 99를 주었다.

근데 하하하 버튼이 가려지지 않고 노출되고 있다.

// Example.jsx

import React from 'react';
import ThankyouDialog from './ThankyouDialog';

export default function Example() {
  return (
    <div>
      <div style={{ position: "absolute"}}
      ><button>하하하</button></div>
      <ThankyouDialog />
    </div>
  )
}

위 코드처럼 ThankyouDialog를 하하하 button 밑으로 배치해야 의도했던 대로 버튼이 가려진다.

이럴 때 Portal이 필요

Portal 사용법

1️⃣ index.html에 id가 root인 div 밑에 <div id="portal"></div> 코드 추가

<!--index.html-->

<div id="root"></div>
<div id="portal"></div> <!-- 추가 -->

2️⃣ Portal Component 생성하고 대상을 Portal로 감싼다.

// Example.jsx

import React from "react";
import { createPortal } from "react-dom";
import ThankyouDialog from "./ThankyouDialog";

const Portal = (props) => {
  return createPortal(props.children, document.getElementById("portal"));
};

export default function Example() {
  return (
    <div>
      <Portal>
        <ThankyouDialog />
      </Portal>
      <div style={{ position: "absolute" }}>
        <button>하하하</button>
      </div>
    </div>
  );
}

이제 하하하 button이 가려진다 !!

중요한 점

Portal 은 root의 영역이 아니라서 아래 코드처럼 div에 onClick 이벤트로 'div'를 console창에 출력하는 작업을 넣고 ThankyouDialog를 클릭하면 출력이 안될 것 같다.

하지만, React는 Portal에 있는 요소라고 하더라도 이 안에서 일어나는 이벤트를 root에 같이 전달해준다... 💡

// Example.jsx
import React from "react";
import { createPortal } from "react-dom";
import ThankyouDialog from "./ThankyouDialog";

const Portal = (props) => {
  return createPortal(props.children, document.getElementById("portal"));
};

export default function Example() {
  return (
    <div onClick={() => console.log("div")}>
      <Portal>
        <ThankyouDialog />
      </Portal>
      <div style={{ position: "absolute" }}>
        <button>하하하</button>
      </div>
    </div>
  );
}

요약

  • createPortal => 부모 컴포넌트 DOM 트리로부터 벗어나기
  • 이벤트 => Portal에 있더라도 Event는 트리로 전파
profile
😁

0개의 댓글