createPortal
createPortal
์ ์ฌ์ฉํ๋ฉด ์ผ๋ถ ์์์ DOM์ ๋ค๋ฅธ ๋ถ๋ถ์ผ๋ก ๋ ๋๋งํ ์ ์์ต๋๋ค.
<div>
<SomeComponent />
{createPortal(children, domNode, key?)}
</div>
๋ ํผ๋ฐ์ค
createPortal(children, domNode, key?)
Portal์ ์์ฑํ๋ ค๋ฉด createPortal
์ ํธ์ถํ์ฌ ์ผ๋ถ JSX์ ๋ ๋๋งํ DOM ๋
ธ๋๋ฅผ ์ ๋ฌํฉ๋๋ค.
import { createPortal } from 'react-dom';
// ...
<div>
<p>This child is placed in the parent div.</p>
{createPortal(
<p>This child is placed in the document body.</p>,
document.body
)}
</div>
์๋์์ ๋ ๋ง์ ์ฌ์ฉ๋ฒ์ ํ์ธํ์ธ์.
Portal์ DOM ๋ ธ๋์ ๋ฌผ๋ฆฌ์ ๋ฐฐ์น๋ง ๋ณ๊ฒฝํฉ๋๋ค. ๋ค๋ฅธ ๋ชจ๋ ๋ฐฉ์์ผ๋ก, portal์ ๋ ๋๋งํ๋ JSX๋ ์ด๋ฅผ ๋ ๋๋งํ๋ React ์ปดํฌ๋ํธ์ ์์ ๋ ธ๋ ์ญํ ์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์์์ ๋ถ๋ชจ ํธ๋ฆฌ๊ฐ ์ ๊ณตํ๋ context์ ์ก์ธ์คํ ์ ์์ผ๋ฉฐ, ์ด๋ฒคํธ๋ React ํธ๋ฆฌ์ ๋ฐ๋ผ ์์์์ ๋ถ๋ชจ๋ก ๋ฒ๋ธ ์ ๋ฉ๋๋ค.
๋งค๊ฐ๋ณ์
-
children
: JSX์ ์ผ๋ถ(์๋ฅผ ๋ค์ด<div />
๋๋<SomeComponent />
), Fragment(<>...</>
), ๋ฌธ์์ด์ด๋ ์ซ์ ๋๋ ์ด๋ค์ ๋ฐฐ์ด๊ณผ ๊ฐ์ด React๋ก ๋ ๋๋งํ ์ ์๋ ๋ชจ๋ ๊ฒ์ ๋๋ค. -
domNode
:document.getElementById()
๊ฐ ๋ฐํํ๋ ๊ฒ๊ณผ ๊ฐ์ ์ผ๋ถ DOM ๋ ธ๋. ๋ ธ๋๊ฐ ๋ฏธ๋ฆฌ ์กด์ฌํด์ผ ํฉ๋๋ค. ์ ๋ฐ์ดํธ ์ค์ ๋ค๋ฅธ DOM ๋ ธ๋๋ฅผ ์ ๋ฌํ๋ฉด portal ์ฝํ ์ธ ๊ฐ ๋ค์ ์์ฑ๋ฉ๋๋ค. -
optional
key
: Portal์ key๋ก ์ฌ์ฉํ ๊ณ ์ ํ ๋ฌธ์์ด ๋๋ ์ซ์์ ๋๋ค.
๋ฐํ ๊ฐ
createPortal
์ JSX์ ํฌํจํ๊ฑฐ๋ React ์ปดํฌ๋ํธ์์ ๋ฐํํ ์ ์๋ React ๋
ธ๋๋ฅผ ๋ฐํํฉ๋๋ค. React๊ฐ ๋ ๋๋ง ์ถ๋ ฅ์์ ์ด๋ฅผ ๋ฐ๊ฒฌํ๋ฉด, ์ ๊ณต๋ children
์ ์ ๊ณต๋ domNode
์์ ๋ฐฐ์นํฉ๋๋ค.
์ฃผ์ ์ฌํญ
- Portal์ ์ด๋ฒคํธ๋ DOM ํธ๋ฆฌ๊ฐ ์๋ React ํธ๋ฆฌ์ ๋ฐ๋ผ ์ ํ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, portal ๋ด๋ถ๋ฅผ ํด๋ฆญํ์ ๋ ํฌํธ์ด
<div onClick>
์ผ๋ก ๊ฐ์ธ์ ธ ์์ผ๋ฉด ํด๋นonClick
ํธ๋ค๋ฌ๊ฐ ์คํ๋ฉ๋๋ค. ์ด๋ก ์ธํด ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด portal ๋ด๋ถ์์ ์ด๋ฒคํธ ์ ํ๋ฅผ ์ค์งํ๊ฑฐ๋ portal ์์ฒด๋ฅผ React ํธ๋ฆฌ์์ ์๋ก ์ด๋ํ์ธ์.
์ฌ์ฉ ๋ฐฉ๋ฒ
DOM์ ๋ค๋ฅธ ๋ถ๋ถ์ผ๋ก ๋ ๋๋งํ๊ธฐ
Portal์ ์ฌ์ฉํ๋ฉด ์ปดํฌ๋ํธ๊ฐ ์ผ๋ถ ์์์ DOM์ ๋ค๋ฅธ ์์น๋ก ๋ ๋๋งํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ์ปดํฌ๋ํธ์ ์ผ๋ถ๊ฐ ์ด๋ค ์ปจํ ์ด๋์ ์๋ ๊ทธ ์ปจํ ์ด๋์์ โํ์ถโํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ปดํฌ๋ํธ๋ ๋ชจ๋ฌ ๋ํ์์๋ ํดํ์ ํ์ด์ง์ ๋๋จธ์ง ๋ถ๋ถ ์์ ์ธ๋ถ์ ํ์ํ ์ ์์ต๋๋ค.
Portal์ ์์ฑํ๋ ค๋ฉด ์ผ๋ถ JSX์ ํจ๊ป createPortal
์ ๊ฒฐ๊ณผ๋ฅผ ๋ ๋๋งํ๊ณ DOM ๋
ธ๋๊ฐ ์์ด์ผ ํ ์์น๋ฅผ ์ง์ ํฉ๋๋ค.
import { createPortal } from 'react-dom';
function MyComponent() {
return (
<div style={{ border: '2px solid black' }}>
<p>This child is placed in the parent div.</p>
{createPortal(
<p>This child is placed in the document body.</p>,
document.body
)}
</div>
);
}
React๋ ์ฌ์ฉ์๊ฐ ์ ๋ฌํ JSX์ ๋ํ DOM ๋ ธ๋๋ฅผ ์ฌ์ฉ์๊ฐ ์ ๊ณตํ DOM ๋ ธ๋ ์์ ๋ฃ์ต๋๋ค.
Portal์ด ์๋ค๋ฉด ๋ ๋ฒ์งธ <p>
๋ ์์ <div>
์์ ๋ฐฐ์น๋์ง๋ง portal์ ์ด๋ฅผ document.body
: ์์ผ๋ก โ์๊ฐ์ด๋โ์ํต๋๋ค.
import { createPortal } from 'react-dom'; export default function MyComponent() { return ( <div style={{ border: '2px solid black' }}> <p>This child is placed in the parent div.</p> {createPortal( <p>This child is placed in the document body.</p>, document.body )} </div> ); }
๋ ๋ฒ์งธ ๋จ๋ฝ์ด ํ
๋๋ฆฌ๊ฐ ์๋ ๋ถ๋ชจ <div>
์ธ๋ถ์ ์๊ฐ์ ์ผ๋ก ์ด๋ป๊ฒ ๋ํ๋๋์ง ์ฃผ๋ชฉํ์ธ์. ๊ฐ๋ฐ์ ๋๊ตฌ๋ก DOM ๊ตฌ์กฐ๋ฅผ ๊ฒ์ฌํ๋ฉด ๋ ๋ฒ์งธ <p>
๊ฐ <body>
์ ๋ฐ๋ก ๋ฐฐ์น๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
<body>
<div id="root">
...
<div style="border: 2px solid black">
<p>This child is placed inside the parent div.</p>
</div>
...
</div>
<p>This child is placed in the document body.</p>
</body>
Portal์ DOM ๋ ธ๋์ ๋ฌผ๋ฆฌ์ ๋ฐฐ์น๋ง ๋ณ๊ฒฝํฉ๋๋ค. ๋ค๋ฅธ ๋ชจ๋ ๋ฉด์์ portal์ ๋ ๋๋งํ๋ JSX๋ ์ด๋ฅผ ๋ ๋๋งํ๋ React ์ปดํฌ๋ํธ์ ์์ ๋ ธ๋ ์ญํ ์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์์์ ๋ถ๋ชจ ํธ๋ฆฌ๊ฐ ์ ๊ณตํ๋ context์ ์ก์ธ์คํ ์ ์์ผ๋ฉฐ ์ด๋ฒคํธ๋ ์ฌ์ ํ React ํธ๋ฆฌ์ ๋ฐ๋ผ ์์์์ ๋ถ๋ชจ๋ก ๋ฒ๋ธ๋ง๋ฉ๋๋ค.
Portal์ด ์๋ ๋ชจ๋ฌ ๋ํ ์์ ๋ ๋๋งํ๊ธฐ
๋ํ ์์๋ฅผ ๋ถ๋ฌ์ค๋ ์ปดํฌ๋ํธ๊ฐ overflow: hidden
๋๋ ๋ํ ์์์ ์ํฅ์ ์ฃผ๋ ๋ค๋ฅธ ์คํ์ผ์ด ์๋ ์ปจํ
์ด๋ ์์ ์๋๋ผ๋ portal์ ์ฌ์ฉํ์ฌ ํ์ด์ง์ ๋๋จธ์ง ๋ถ๋ถ ์์ ๋ ์๋ ๋ชจ๋ฌ ๋ํ ์์๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
์ด ์์์์ ๋ ์ปจํ ์ด๋์๋ ๋ชจ๋ฌ ๋ํ ์์์ ์ํฅ์ ์ฃผ๋ ์คํ์ผ์ด ์์ง๋ง, portal์ ๋ ๋๋ง๋ ์คํ์ผ์ ์ํฅ์ ๋ฐ์ง ์๋๋ฐ, ๊ทธ ์ด์ ๋ DOM์์ ๋ชจ๋ฌ์ด ์์ JSX ์์์ ํฌํจ๋์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
import NoPortalExample from './NoPortalExample'; import PortalExample from './PortalExample'; export default function App() { return ( <> <div className="clipping-container"> <NoPortalExample /> </div> <div className="clipping-container"> <PortalExample /> </div> </> ); }
React ์ปดํฌ๋ํธ๋ฅผ React๊ฐ ์๋ ์๋ฒ ๋งํฌ์ ์ผ๋ก ๋ ๋๋งํ๊ธฐ
Portal์ React ๋ฃจํธ๊ฐ React๋ก ๋น๋๋์ง ์์ ์ ์ ๋๋ ์๋ฒ ๋ ๋๋ง ํ์ด์ง์ ์ผ๋ถ์ผ ๋ ์ ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ํ์ด์ง๊ฐ Rails์ ๊ฐ์ ์๋ฒ ํ๋ ์์ํฌ๋ก ๋น๋๋ ๊ฒฝ์ฐ ์ฌ์ด๋๋ฐ์ ๊ฐ์ ์ ์ ์์ญ ๋ด์ ์ธํฐ๋ํฐ๋ธ ์์ญ์ ๋ง๋ค ์ ์์ต๋๋ค. ์ฌ๋ฌ ๊ฐ์ ๊ฐ๋ณ React ๋ฃจํธ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋น๊ตํ์ฌ, portal์ ์ฌ์ฉํ๋ฉด ์ฑ์ ์ผ๋ถ๊ฐ DOM์ ๋ค๋ฅธ ๋ถ๋ถ์ ๋ ๋๋ง ๋๋๋ผ๋ ๊ณต์ ์ํ๋ฅผ ๊ฐ์ง ๋จ์ผ React ํธ๋ฆฌ๋ก ์ทจ๊ธํ ์ ์์ต๋๋ค.
import { createPortal } from 'react-dom'; const sidebarContentEl = document.getElementById('sidebar-content'); export default function App() { return ( <> <MainContent /> {createPortal( <SidebarContent />, sidebarContentEl )} </> ); } function MainContent() { return <p>This part is rendered by React</p>; } function SidebarContent() { return <p>This part is also rendered by React!</p>; }
React ์ปดํฌ๋ํธ๋ฅผ React๊ฐ ์๋ DOM ๋ ธ๋๋ก ๋ ๋๋งํ๊ธฐ
Portal์ ์ฌ์ฉํด React ์ธ๋ถ์์ ๊ด๋ฆฌ๋๋ DOM ๋
ธ๋์ ์ฝํ
์ธ ๋ฅผ ๊ด๋ฆฌํ ์๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, React๊ฐ ์๋ ๋งต ์์ ฏ๊ณผ ํตํฉํ๊ณ ํ์
์์ React ์ฝํ
์ธ ๋ฅผ ๋ ๋๋งํ๊ณ ์ถ๋ค๊ณ ๊ฐ์ ํด ๋ด
์๋ค. ์ด๋ ๊ฒ ํ๋ ค๋ฉด ๋ ๋๋งํ DOM ๋
ธ๋๋ฅผ ์ ์ฅํ popupContainer
์ํ ๋ณ์๋ฅผ ์ ์ธํ์ธ์.
const [popupContainer, setPopupContainer] = useState(null);
์๋ํํฐ ์์ ฏ์ ๋ง๋ค ๋ ์์ ฏ์ด ๋ฐํํ๋ DOM ๋ ธ๋๋ฅผ ์ ์ฅํ์ฌ ๋ ๋๋งํ ์ ์๋๋ก ํฉ๋๋ค.
useEffect(() => {
if (mapRef.current === null) {
const map = createMapWidget(containerRef.current);
mapRef.current = map;
const popupDiv = addPopupToMapWidget(map);
setPopupContainer(popupDiv);
}
}, []);
์ด๋ ๊ฒ ํ๋ฉด createPortal
์ ์ฌ์ฉํ์ฌ React ์ฝํ
์ธ ๊ฐ ์ฌ์ฉ ๊ฐ๋ฅํด์ง๋ฉด popupContainer
๋ก ๋ ๋๋งํ ์ ์์ต๋๋ค.
return (
<div style={{ width: 250, height: 250 }} ref={containerRef}>
{popupContainer !== null && createPortal(
<p>Hello from React!</p>,
popupContainer
)}
</div>
);
๋ค์์ ์คํํ ์ ์๋ ์ ์ฒด ์์ ์ ๋๋ค.
import { useRef, useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { createMapWidget, addPopupToMapWidget } from './map-widget.js'; export default function Map() { const containerRef = useRef(null); const mapRef = useRef(null); const [popupContainer, setPopupContainer] = useState(null); useEffect(() => { if (mapRef.current === null) { const map = createMapWidget(containerRef.current); mapRef.current = map; const popupDiv = addPopupToMapWidget(map); setPopupContainer(popupDiv); } }, []); return ( <div style={{ width: 250, height: 250 }} ref={containerRef}> {popupContainer !== null && createPortal( <p>Hello from React!</p>, popupContainer )} </div> ); }