// child : 그리고자 하는 ReactNode
// container : 어디에 그릴지 실제 DOM 전달
ReactDOM.createPortal(child: React.ReactNode, container: HTMLElement)
react root DOM node 외부에 React Element 그리기
일반적인 경우 ReactDOM.render나 hydrate을 통해 dom을 그리게 되는데, 이외에 경우에 사용
modal(페이지 이동 X, 화면 전체를 덮는), tooltip, 외부 layer등에 사용하기 좋음 -> React가 관리하지 않는 외부의 dom일 경우 사용
Portal Component에서 발생한 event도 부모에 전달 됨. -> 일반 react child 처럼 사용 가능한 장점
@08-portal/portal
import { useState } from 'react';
import ReactDOM from 'react-dom';
// 1번째 인자 : 내가 그리고 싶은 React Element
// 2번째 인자 : 어디에 그릴지
const Modal = () => ReactDOM.createPortal(
<div>I am a modal</div>,
document.body.querySelector('#app')
)
const App = () => {
const [count, setCount] = useState(0);
return (
<div onClick={() => setCount(count + 1)}>
<div>Hello!</div>
<div>count: {count}</div>
{/* 구조상으로는 위의 영역 안에 들어가있는 것 같지만, 실제 DOM을 보면 #root 밖의 #app에 그려져 있음 */}
{/* Virtual DOM상으로는 지금 보이는 구조대로 들어가기 때문에 Modal에서 발생한 click event도 부모 div에서 catch가 가능한 점이 장점, 내부 state를 공유해서 사용 가능 */}
{/* React가 아니라면 Modal에 직접 click 이벤트를 또 걸어줘야 하는 불편함 */}
<Modal />
</div>
)
}
export default App
import { useState, useEffect, useCallback, useRef } from 'react';
import ReactDOM from 'react-dom';
import copyStyles from './copyStyles';
function MyWindowPortal({ children, closeWindowPortal }) {
const containerElRef = useRef(null);
// 외부 DOM이기 때문에 찾는 로직
if (!containerElRef.current) {
// STEP 1: create an empty div
containerElRef.current = document.createElement('div');
}
useEffect(() => {
const containerEl = containerElRef.current
// STEP 3: open a new browser window
const externalWindow = window.open('', '', 'width=600,height=400,left=200,top=200');
// STEP 4: append the container <div> to the body of the new window
externalWindow.document.body.appendChild(containerEl);
externalWindow.document.title = 'A React portal window';
copyStyles(document, externalWindow.document); // 현재 documnet와 외부 document 스타일을 맞춤 (그냥 따로 만든 로직)
// update the state in the parent component if the user closes the new window
// 닫히기 직전에 setShowWindowPortal로 내부 state로 닫혔음을 update
externalWindow.addEventListener('beforeunload', closeWindowPortal);
// store containterEl to ref
containerElRef.current = containerEl;
// This will fire when showWindowPortal in the parent component becomes false
// So we tidy up by just closing the window
return () => {
externalWindow.close();
}
}, [closeWindowPortal])
// STEP 2: draw children to containerEl
return ReactDOM.createPortal(children, containerElRef.current);
}
function App() {
const [count, setCount] = useState(0);
const [showWindowPortal, setShowWindowPortal] = useState(false); // Portal을 보여줄지 말지
// 함수를 다시 생성할 필요가 없어서 useCallback 감싸서 사용 (아래에서 dependency로 사용)
const toggleWindowPortal = useCallback(() => {
setShowWindowPortal(prev => !prev);
}, [])
const closeWindowPortal = useCallback(() => {
setShowWindowPortal(false);
}, [])
useEffect(() => {
const interval = window.setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => {
window.clearInterval(interval);
closeWindowPortal(); // 컴포넌트가 unmount되면 Portal로 열린 Modal을 닫음
}
}, [closeWindowPortal])
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={toggleWindowPortal}>
{showWindowPortal ? 'Close the' : 'Open a'} Portal
</button>
{showWindowPortal && (
// 위에서 정의한 closeWindowPortal을 그대로 전달해서 사용 가능
// 내부 Element들 모두 children으로 전달됨
<MyWindowPortal closeWindowPortal={closeWindowPortal}>
{/* 위의 count와 같은 count 공유 사용 (외부 props, state 공유시 유용한 Portal) */}
<h1>Counter in a portal: {count}</h1>
<p>Even though I render in a different window, I share state!</p>
<button onClick={() => closeWindowPortal()}>
Close me!
</button>
</MyWindowPortal>
)}
</div>
);
}
export default App;
Portal
modal
(페이지 이동 X, 화면 전체를 덮는),tooltip
,외부 layer
등에 사용하기 좋음 -> React가 관리하지 않는 외부의 dom일 경우 사용Portal Component
에서 발생한 event도 부모에 전달 됨. -> 일반 react child 처럼 사용 가능한 장점@08-portal/portal
활용예시: codepen
@08-portal/windowPortal