Portal
Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공합니다.
리액트 포탈(React Portal)은 리액트(React) 라이브러리에서 제공하는 기능 중 하나로, React 컴포넌트에서 DOM 트리 상에서의 위치를 변경할 수 있도록 해줍니다. 이 기능을 사용하면 컴포넌트의 렌더링 결과를 다른 DOM 노드로 이동시킬 수 있습니다.
ReactDOM.createPortal(child, container)
첫 번째 인자(child)는 엘리먼트, 문자열, 혹은 fragment와 같은 어떤 종류이든 렌더링할 수 있는 React 자식입니다.
두 번째 인자(container)는 DOM 엘리먼트입니다.
리액트 포탈의 장점과 사용시점??
장점:
- 유연한 구성: 리액트 포탈은 컴포넌트를 다른 DOM 위치로 이동시키기 때문에 UI 구성을 더욱 유연하게 할 수 있습니다.
예를 들어, 모달(Modal) 창을 구현할 때 일반적으로는 모달 컴포넌트를 최상위 컴포넌트의 자식으로 추가해야 합니다. 하지만 리액트 포탈을 사용하면 모달 컴포넌트를 원하는 위치로 이동시킬 수 있으므로, 보다 복잡한 UI 구성도 쉽게 구현할 수 있습니다. - 성능 최적화: 리액트 포탈은 컴포넌트의 렌더링 결과를 다른 DOM 위치로 이동시키는 기능을 제공하므로, 컴포넌트의 렌더링이 최적화될 수 있습니다. 예를 들어, 상위 컴포넌트에서 자식 컴포넌트를 렌더링하면서 상태(State)를 변경하면, 해당 컴포넌트와 그 자식 컴포넌트가 모두 다시 렌더링됩니다. 하지만 리액트 포탈을 사용하면 해당 컴포넌트와 그 자식 컴포넌트 중에서 변경된 부분만 렌더링되므로, 불필요한 렌더링을 최소화할 수 있습니다.
사용 시점:
- 모달(Modal) dialog: 모달 창은 일반적으로 최상위 컴포넌트의 자식으로 추가됩니다. 하지만 리액트 포탈을 사용하면 모달 창을 다른 위치로 이동시킬 수 있으므로, 보다 유연하고 복잡한 UI 구성도 쉽게 구현할 수 있습니다.
- 포털(Portal): 포털은 일반적으로 다른 웹 페이지로 이동하는 링크를 클릭할 때, 새로운 브라우저 탭이나 창을 열어서 해당 페이지를 보여주는 기능을 말합니다.
⛔️ 주의
portal을 이용하여 작업할 때 키보드 포커스 관리가 매우 중요하다는 것을 염두에 두세요.
예) esc 누르면 modal dialog가 닫혀지게 함
export function Notification({
show,
onClose,
className,
children,
...restProps
}) {
//esc 누르면 notification이 닫혀지게 함
const closeButtonRef = useRef(null);
useEffect(() => {
const { current: closeButton } = closeButtonRef;
if (closeButton) {
const handleKeyUp = (e) => {
console.log(e.key);
if (e.key.toLowerCase() === 'escape') {
onClose();
}
};
// 버튼 요소에 이벤트를 연결하면
// 초점이 버튼 요소에 적용되었을때 그때 이벤트가 발동
globalThis.addEventListener('keyup', handleKeyUp);
//cleanup
return () => globalThis.removeEventListener('keyup', handleKeyUp);
}
}, [onClose, show]);
return show
? createPortal(
<div
className={classNames(classes.Notification, className)}
{...restProps}
>
{children}
<button
ref={closeButtonRef}
type="button"
className={classes.closeButton}
onClick={onClose}
>
×
</button>
</div>,
notificationContainer
)
: null;
}
ErrorModal dialog 구현 예시 ( udemy 강의 참고)
<!DOCTYPE html>
<html lang="ko-K">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="create-react-app으로 만든 웹사이트" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>이 앱을 실행하려면 JavaScript를 활성화해야 합니다.</noscript>
<!-- Portal -->
<div id="backdrop-root"></div>
<div id="overlay-root"></div>
<div id="root"></div>
</body>
</html>
ErrorModal.jsx
import React from "react";
import Card from "./Card";
import Button from "./Button";
import classes from "./ErrorModal.module.css";
import { createPortal } from "react-dom";
const Backdrop = ({ onConfirm }) => {
return <div className={classes.backdrop} onClick={onConfirm}></div>;
};
const ModalOverlay = ({ onConfirm, title, message }) => {
return (
<Card className={classes.modal}>
<header className={classes.header}>
<h2>{title}</h2>
</header>
<div className={classes.content}>
<p>{message}</p>
</div>
<footer className={classes.actions}>
<Button onClick={onConfirm}>Okay</Button>
</footer>
</Card>
);
};
export function ErrorModal({ onConfirm, title, message }) {
const backdropRoot = document.getElementById("backdrop-root");
const overlayRoot = document.getElementById("overlay-root");
return (
<>
{/* Portal */}
{createPortal(<Backdrop onConfirm={onConfirm} />, backdropRoot)}
{createPortal(
<ModalOverlay title={title} message={message} onConfirm={onConfirm} />,
overlayRoot
)}
</>
);
}
export default ErrorModal;
참고 사이트
https://beta.reactjs.org/reference/react-dom/createPortal
https://ko.reactjs.org/docs/portals.html
'React' 카테고리의 다른 글
리액트 라우터 outlet 사용하여 중첩된 레이아웃 구성 (0) | 2023.03.09 |
---|---|
Custom Hook 만들기 (0) | 2023.03.01 |
useState & useEffect 비교 (0) | 2023.03.01 |
DOM(돔) vs Virtual DOM(가상 돔) (0) | 2023.02.14 |
composition의 개념 ('children prop') (0) | 2022.11.22 |