React Portal

창건·2022년 7월 9일
0

리액트

목록 보기
2/5

React Portal?

공식 문서에 따르면

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

이라고 하는데... 설명이 무척 어렵게 느껴진다. 😓

예시를 통해 무엇을 의미하는지 알아보자

모달 컴포넌트

다음과 같이 간단히 모달 컴포넌트를 만들어 보았다.

BackDrop(배경)과 ModalBody(실제 모달)로 구성되어 있다.

import React from 'react';
import styled from 'styled-components';

const BackDrop = styled.div`
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 10;
  background: rgba(0, 0, 0, 0.75);
`;

const ModalBody = styled.div`
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 100;
  overflow: hidden;
`;

const Modal = (props) => {
  return (
    <>
      <BackDrop onClick={props.closeModal} />
      <ModalBody>
        <button onClick={props.closeModal}>close modal</button>
      </ModalBody>
    </>
  );
};

export default Modal;

App.js에서 모달이 열려있는지(modalOpen) 상태관리를 하며 렌더링 해준다.

import { useState } from 'react';
import styled from 'styled-components';
import Modal from './components/Modal';

const Wrapper = styled.div`
  margin: auto;
  width: 500px;
  height: 500px;
  display: flex;
  justify-content: center;
  align-items: center;
`;

function App() {
  // 모달이 열려있는지
  const [modalOpen, setModalOpen] = useState(false);

  return (
    <Wrapper>
      {modalOpen && (
        <Modal
          closeModal={() => {
            setModalOpen(false);
          }}
        />
      )}
      <button
        onClick={() => {
          setModalOpen(true);
        }}
        type='button'
      >
        Open Modal
      </button>
    </Wrapper>
  );
}

export default App;

문제점

이처럼 모달 컴포넌트를 만들면, 브라우저는 다음과 같이 렌더링한다.

자세히 보면, 모달 컴포넌트가 모달을 여는 버튼과 형제 요소로 배치되어 있는 것을 볼 수 있다.

딱히 오류를 발생시키는 것은 아니지만, semantic한 측면에서는 형제 관계라고 말하기 어렵다.

Portal의 역할

Portal은 부모 컴포넌트의 DOM 계층 구조 바깥의 DOM 노드로 자식 엘리먼트를 렌더링하게 해준다고 했다.

즉, 우리의 모달을 바깥으로 넘겨, body태그 바로 아래에 위치시킬 수 있다.

사용 방법

public폴더의 index.html에 div 태그를 만들고, id를 원하는 대로 설정한다. 이곳이 모달 컴포넌트가 렌더링될 위치이다.

  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="modal"></div>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>

App.js에서 react-dom을 import한다.
그리고, createPortal() 메소드를 사용한다.
첫번째 인자로 렌더링할 컴포넌트를, 두번째 인자로 노드를 받는다.

import ReactDOM from 'react-dom';

// 코드 생략

function App() {
  const [modalOpen, setModalOpen] = useState(false);

  return (
    <Wrapper>
      {modalOpen &&
        ReactDOM.createPortal(
          <Modal
            closeModal={() => {
              setModalOpen(false);
            }}
          />,
          document.getElementById('modal')
        )}
      <button
        onClick={() => {
          setModalOpen(true);
        }}
        type='button'
      >
        Open Modal
      </button>
    </Wrapper>
  );
}

export default App;

결과

id가 root인 div태그가 dom트리에 생성되고, 모달 컴포넌트가 렌더링 된다.

profile
피곤한만큼 성장할 수 있으면

0개의 댓글