Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공한다.
React는 부모 컴포넌트가 렌더링 되면서 자식 컴포넌트가 렌더링되는 트리 구조로 되어있다.
이러한 트리 구조가 불편함을 줄 때가 있고, 부모 - 자식 관계를 유지하지만 독립적인 위치에서 렌더링 할 때 더욱 편한 경우가 있다. 또한 스타일링을 통해 z-index
와 같은 후 처리를 해주어야 한다.
또한 기본적으로 모달은 모든 페이지위에 오버레이 된다. 하지만 스크린리더가 렌더링되는 HTML코드를 해석할 때 모달이라는 존재를 인식할 수 없게 된다.
또한 의미적이나 구조적인 관점에서 모달이 모든 영역 위에 깔린 것인지 알지 못한다.
이러한 상황에서 Portal을 통해 독립적인 구조와 부모 - 자식 관계를 유지할 수 있다.
import ReactDOM from 'react-dom';
const element = document.getElementById('modal') as HTMLElement;
ReactDOM.createPortal(children, element);
createPortal이 정의되어 있는 ReactDOM을 불러온다.
첫 번째 인자(child)는 엘리먼트, 문자열, 혹은 fragment와 같은 어떤 종류이든 렌더링할 수 있는 React 자식이다.
두 번째 인자(container)는 DOM 엘리먼트이다.
// public/index.html
<body>
<div id="root"></div>
<div id="modal"></div>
</body>
Portal을 구현 할 트리의 부모 컴포넌트를 어디로 정할지 설정한다.
위 코드에선 최상단 요소인 root의 형제관계로 modal을 넣어주었다.
// ModalPortal.tsx
import { ReactNode } from 'react';
import ReactDOM from 'react-dom';
interface Props {
children: ReactNode;
}
const ModalPortal = ({ children }: Props) => {
const element = document.getElementById('modal') as HTMLElement;
return ReactDOM.createPortal(children, element);
};
export default ModalPortal;
// Modal.tsx
import styles from '../styles/Modal.module.css';
interface Props {
onClose: () => void;
}
const Modal = ({ onClose }: Props) => {
return (
<div>
<div className={styles['modal-overlay']} onClick={onClose}></div>
<div className={styles['modal-content-container']}>
<div className={styles['modal-content']}>
<h1>모달테스트</h1>
<button
onClick={onClose}
className={styles['modal-close-button']}
type='button'
>
닫기
</button>
</div>
</div>
</div>
);
};
export default Modal;
// App.tsx
import { useState } from 'react';
import Modal from './components/Modal';
import ModalPortal from './components/ModalPortal';
const App = () => {
const [modalOpen, setModalOpen] = useState<boolean>();
const showModal = () => {
setModalOpen(true);
};
const closeModal = () => {
setModalOpen(false);
}
return (
<div>
<button onClick={showModal}>모달 열기</button>
{modalOpen && (
<ModalPortal>
<Modal onClose={closeModal}/>
</ModalPortal>
)}
</div>
);
};
export default App;
Portal 시키고자 하는 요소를 <ModalPortal>
컴포넌트로 감싸 준다.
또한 이벤트 핸들러를 달아주어 검은색 배경과 모달의 버튼 클릭시 모달이 닫히게 하였다.