작은 규모의 리액트 앱에서 작업하다보면 Portals
의 필요성이 크게 느껴지지 않을 수 있다. 하지만 점점 앱의 규모가 커지다보면 Component
의 구조가 복잡해짐에 따라 중첩 등의 문제가 발생할 수 있다.
공식 문서에 따르면 Portals
을 주로 사용하는 환경은 부모 컴포넌트에 overflow: hidden
이나 z-index
가 있는 경우지만, 다이얼로그, 호버카드 그리고 툴팁 등 시각적으로 자식 컴포넌트를 해당 컨테이너에서 튀어나오게 보여주기 위해서 필요하기도 하다.
유저 이름 및 나이를 관리하는 미니 프로젝트를 작업 중에 있다. 유효성 검사를 통해 조건에 맞지 않는 값을 입력하거나 아예 입력하지 않을 시 Error Modal
이 나타나게 만들어져있다.
여기서 이 Modal
이 예로 들기 딱 좋은 형태이다! 렌더링 되고 있는 실제 DOM을 살펴보자면 Error Modal
이 트리거 될 때 root div
내에서 Backdrop, Card, Modal Overlay 등과 관련된 Modal div
들이 form 을 감싸고 있는 Card
요소 옆에서 렌더링 된다.
<Modal 트리거 전>
<Modal 트리거 후>
하지만 만약 해당 Modal
이 실행되는 Component
가 앱의 최상단과 가깝지 않고 다른 Component
와 깊게 중첩되어 있다면 Backdrop
과 Modal div
또한 DOM 내의 다른 콘텐츠와 깊게 중첩될 수 있다.
이를 해소하기 위해서는 아래의 이미지와 같이 Modal
과 Backdrop
이 실행되는 위치를 body
바로 아래로 위치시켜서 body
의 직접적인 자식으로 만들고 나머지 앱을 포함하는 root div
옆에 배치하면 된다.
Modal
및 Backdrop
생성 위치 변경
Portals 을 통해서 위와 같이 만들 수 있다.
Portals
을 사용하기 위해서는 2가지를 기억 해야한다.
1) public/index.html
로 이동
2) body 내부에 <div>
요소를 작성하고 id
를 추가 (이식 해야하는 다양한 Component 에 대한 여러 root 들을 만들 수 있음)
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<!-- 이곳에 이식 해야하는 다양한 Component 에 대한 여러 root 들을 만들 수 있다. -->
<div id="backdrop-root"></div>
<div id="overlay-root"></div>
<div id="root"></div>
</body>
3) src/ErrorModal.js
에서 Backdrop
과 ModalOverlay
각각의 Componenet
를 생성
(한 파일에서 2개의 Component 를 생성하였는데 그 이유는 2가지 기능이 함께 사용 중이며, Portal 로 다루기 간편하기 때문)
import Card from "./Card";
import Button from "./Button";
import classes from "./ErrorModal.module.css";
const Backdrop = (props) => {
return <div className={classes.backdrop} onClick={props.onConfirm} />;
};
const ModalOverlay = (props) => {
return (
<Card className={classes.modal}>
<header className={classes.header}>
<h2>{props.title}</h2>
</header>
<div className={classes.content}>
<p>{props.message}</p>
</div>
<footer className={classes.actions}>
<Button onClick={props.onConfirm}>Okay</Button>
</footer>
</Card>
);
};
4) 동일한 src/ErrorModal.js
하단에 실제 다른 파일로 import
되어 사용하는 Component 인 ErrorModal
도 생성
import React from "react";
import ReactDOM from "react-dom";
***
// 3) 번 코드
***
const ErrorModal = (props) => {
return (
<React.Fragment>
{ReactDOM.createPortal(
<Backdrop onConfirm={props.onConfirm} />,
document.getElementById("backdrop-root")
)}
{ReactDOM.createPortal(
<ModalOverlay
title={props.title}
message={props.message}
onConfirm={props.onConfirm}
/>,
document.getElementById("overlay-root")
)}
</React.Fragment>
);
};
export default ErrorModal;
🤔 react-dom 을 import 한 이유
- react-dom 은 React 를 사용해 로직과 각종 기능들을 웹 브라우저로 가져오고, DOM 과 호환되도록 해주기 때문이다.
🤔 DOM API 구조
ReactDOM.createPortal( <Backdrop onConfirm={props.onConfirm} />, document.getElementById("backdrop-root") )}
- 첫 번째 인수(
<Backdrop onConfirm={props.onConfirm} />
) 는 렌더링 되어야 하는 리액트 노드- 두 번째 인수(
document.getElementById("backdrop-root")
) 는 요소들이 렌더링 되어야 하는 실제 DOM 안의 컨테이너를 가리키는 포인터
위의 과정을 거쳐서 Portals 설정을 완료해주면 아래 이미지와 같이 <div id="root>
요소 내에서 Backdrop
과 ModalOverlay
가 실행 되는 것이 아닌 <body>
하위에서 <div id="root>
와 동일한 선상에서 실행이 되는 것을 확인할 수 있다.
Backdrop
ModalOverlay
렌더링 하고자 하는 컴포넌트를 다른 위치로 옮기는 것이 Portals 의 핵심이라고 할 수 있다. 이 때 createPortal
과 같은 DOM API 를 사용하여 이동시켜 렌더링 할 수 있다. 해당 앱 외부가 아니라 앱 내부에서도 원하는 곳에 렌더링 하고 싶을 때 사용할 수 있다.
DOM 트리 어느 곳에서도 위치할 수 있으나 마치 일반적으로 실행되는 자식 컴포넌트처럼 작용한다.
Udemy 강의 (React 완벽 가이드)
React 공식 문서
velog@velopert