[React] React Portal이란, React Portal로 modal 구현

김채운·2023년 1월 19일
11

React

목록 보기
15/26

왜 새로운 게 끝도 없이 나오는거지...? React Portal은 진짜 생전 첨 들어보는데 인강 듣다가 알게돼서 기록해두기 위해 끄적여본다...

React Portal이란?

공식 문서에 따르면 Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공한다고 되어있다.
즉 외부에 존재하는 DOM 노드가 React App DOM 계층 안에 존재하는 것처럼 연결을 해주는 포탈 기능을 제공합니다.
이 말은, 컴포넌트들의 상하 관계, 구조를 가지고 있는 문서인 DOM 상에서, 자식 컴포넌트를 부모 컴포넌트 바깥에 있는 다른 컴포넌트에 전달할 수 있다는 뜻이다.

위의 이미지를 보면 root와 modal이 형제 element처럼 보이지만, 사실은 modal div는 root div 안에 존재하는 자식 컴포넌트 이다. 단지 렌더링만 root의 바깥에서 이뤄지고 있는 것이다.

Portal은 보통, React의 index.html의 #root div 외에, 다른 루트에 Modal, Dialog, Tooltip 등을 띄우기 위해 많이 사용된다.

Portal 사용의 이유

➡️ React 의 tree 구조에 따라서 부모 컴포넌트가 렌더링 되면 자식 컴포넌트도 그 영향을 받아서 같이 렌더링이 된다. 그래서 자식 컴포넌트 같은 경우는 쓸데없는 렌더링이 일어날 수도 있고 부모 컴포넌트의 스타일링에 제약을 받아 z-index 와 같은 후처리를 해야한다는 점도 있다.
이러한 DOM tree 상의 부모-자식 컴포넌트 간의 제약에서 Portal을 통해 독립적인 구조와 부모-자식 관계를 동시에 유지할 수 있기 때문에 Portal을 사용하는 것이다.

➡️ 또 다른 이유로는 modal이 독립적이지 않고 부모의 DOM노드 안에서 렌더링 되는 것이 의미적인 관점이나 간결한 HTMl 구조를 갖췄는지의 관점으로 보면 별로 좋지 않기 때문이다.
왜냐, 기본적으로 모달은 페이지 위에 표시되는 오버레이이기 때문이다. 모달은 전체페이지에 대한 오버레이이기 때문에 당연히 다른 모든 것의 위에 있다. 그래서 모달이 만약 다른 HTML 코드 안에 중첩되어 있다면, 기술적으로는 스타일링 덕분에 작동할지 몰라도 좋은 코드가 아니고 좋은 구조 또한 아니게 된다.
예를들어 스크린 리더가 렌더링 되는 HTML 코드를 해석할 때 일반적이 오버레이라고 인식하지 못할 수도 있다. 또한 의미적인 관점이나 구조적인 관점으로 보면 이것은 HTML 코드 안 깊은 곳에 자리잡고 있기 때문에 이 모달이 다른 모든 내용에 대한 오버레이인지 명확하지 않다.
그래서 리액트 개념에서 오버레이 내용이 있는 모달이 깊게 중첩되면 안 되는 문제를 해결할 수 있게끔 한 방법이 React Portal인 것이다.

Portal 사용법

ReactDom.createPortal(child, container)
  • ReactDom의 createPortal 메서드를 이용해서 원하는 컴포넌트를 Portal 시킬 수 있다.
  • 첫 번째 인자는 엘리먼트, 문자열, 혹은 fragment같은 어떤 종류이든 렌더링 할 수 있는 React의 자식이 들어가고 두 번째 인자는 DOM element가 들어간다. 두 번째 인자 container 는 이 Portal의 목적지를 뜻한다고 생각하면 된다. 우리가 child 에 넣어둔 컴포넌트는 렌더링될 때 가까운 부모 컴포넌트가 아닌 우리가 container 에 설정해둔 컴포넌트에 랜더링되게 된다.

Portal로 Modal 구현하기

최근에 완성했던 오픈마켓 프로젝트에 적용시켜 보았다.

➡️ 모달이 렌더링 될 위치 만들어주기.

public/index.html

<body>
  <div id="root"></div>
  <div id="modal"></div>
</body>
  • portal을 구현할 tree의 부모 컴포넌트를 어디로 설정할지 정하는 것.
    위 코드에선 기존 최상단 요소인 root의 형제관계로 modal 요소를 넣었다.
  • id가 modal인 div 엘리먼트에서 모달이 렌더링 되도록 한다.

➡️ Portal 만들기

src/helpers/Portal.js

import ReactDom from "react-dom";

const ModalPortal = ({ children }) => {
    const el = document.getElementById("modal")
    return ReactDom.createPortal(children, el)
}

export default ModalPortal
  • modal 컴포넌트를 Portal 시켜줄 Portal.js를 만들어준다.
  • 첫 번째 인자인 children으로 modal을 넣어줘서 Portal 시킬 것이다.
  • 두 번째 인자인 el은 document.getElementById("modla")로 idnex.html의 id가 modal인 div 요소로 연결시켜주는 역할을 한다.

➡️ Modal이 있는 페이지에 Portal 연결시키기

src/pages/ProductDetail.js (상품상세페이지)

//components
import UserModal from '../components/UserModal';
//helpers
import ModalPortal from '../helpers/Portal';

function ProductDetail() {
    return (
        <div>
            <ModalPortal>
                {
                    modal === 1 ?
                        <UserModal modal_to_check
                            display="none"
                            children2="이미 장바구니에 있는 상품입니다."
                            children3="장바구니로 이동하시겠습니까?"
                            btn_children_1="아니오"
                            btn_children_2="예"
                            margin="40px 0 0 0"
                            _onClick={() => setModal(0)}
                            _onClick2={modalAddCart}
                            _onClickBg={() => setModal(0)}
                        /> :
                        modal === 2 &&
                        <UserModal modal_to_check
                            _disabled={true}
                            children2="로그인이 필요한 서비스입니다."
                            children3="로그인 하시겠습니까?"
                            btn_children_1="아니오"
                            btn_children_2="예"
                            margin="30px 0 0 0"
                            _onClick={() => setModal(0)}
                            _onClick2={() => {
                                navigate("/login")
                            }}
                            _onClickBg={() => setModal(0)}
                        />
                }
            </ModalPortal>
        </div>
}
  • Portal 시키고자 하는 modal에 위에서 만들어준 ModalPortal을 import 해와서 modal을 감싸준다.

➡️ 결과

  • modal이 root의 형제 요소인 modal 안에서 렌더링 되는 걸 확인할 수 있다.
  • modal이 나타나기 전에는 modal이 렌더링 될 요소에 아무것도 포함되어있지 않다.
  • modal이 나타나고 나서 div 안에 modal 코드가 포함된 걸 확인 할 수 있다.

출처

0개의 댓글