Portals
Portal์ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ DOM ๊ณ์ธต ๊ตฌ์กฐ ๋ฐ๊นฅ์ ์๋ DOM ๋ ธ๋๋ก ์์์ ๋ ๋๋งํ๋ ์ต๊ณ ์ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
ReactDOM.createPortal(child, container)
์ฒซ ๋ฒ์งธ ์ธ์(child
)๋ ์๋ฆฌ๋จผํธ, ๋ฌธ์์ด, ํน์ fragment์ ๊ฐ์ ์ด๋ค ์ข
๋ฅ์ด๋ ๋ ๋๋งํ ์ ์๋ React ์์์
๋๋ค. ๋ ๋ฒ์งธ ์ธ์(container
)๋ DOM ์๋ฆฌ๋จผํธ์
๋๋ค.
์ฌ์ฉ๋ฒ
๋ณดํต ์ปดํฌ๋ํธ ๋ ๋๋ง ๋ฉ์๋์์ ์๋ฆฌ๋จผํธ๋ฅผ ๋ฐํํ ๋ ๊ทธ ์๋ฆฌ๋จผํธ๋ ๋ถ๋ชจ ๋ ธ๋์์ ๊ฐ์ฅ ๊ฐ๊น์ด ์์์ผ๋ก DOM์ ๋ง์ดํธ๋ฉ๋๋ค.
render() {
// React๋ ์๋ก์ด div๋ฅผ ๋ง์ดํธํ๊ณ ๊ทธ ์์ ์์์ ๋ ๋๋งํฉ๋๋ค.
return (
<div> {this.props.children}
</div> );
}
๊ทธ๋ฐ๋ฐ ๊ฐ๋ DOM์ ๋ค๋ฅธ ์์น์ ์์์ ์ฝ์ ํ๋ ๊ฒ์ด ์ ์ฉํ ์ ์์ต๋๋ค.
render() {
// React๋ ์๋ก์ด div๋ฅผ ์์ฑํ์ง *์๊ณ * `domNode` ์์ ์์์ ๋ ๋๋งํฉ๋๋ค.
// `domNode`๋ DOM ๋
ธ๋๋ผ๋ฉด ์ด๋ ํ ๊ฒ์ด๋ ์ ํจํ๊ณ , ๊ทธ๊ฒ์ DOM ๋ด๋ถ์ ์ด๋์ ์๋ ์ง ์๊ด์์ต๋๋ค.
return ReactDOM.createPortal(
this.props.children,
domNode );
}
portal์ ์ ํ์ ์ธ ์ ์ค์ผ์ด์ค๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ overflow: hidden
์ด๋ z-index
๊ฐ ์๋ ๊ฒฝ์ฐ์ด์ง๋ง, ์๊ฐ์ ์ผ๋ก ์์์ โํ์ด๋์ค๋๋กโ ๋ณด์ฌ์ผ ํ๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ค์ด์ผ๋ก๊ทธ, ํธ๋ฒ์นด๋๋ ํดํ๊ณผ ๊ฐ์ ๊ฒ์
๋๋ค.
์ฃผ์
portal์ ์ด์ฉํ์ฌ ์์ ํ ๋ ํค๋ณด๋ ํฌ์ปค์ค ๊ด๋ฆฌ๊ฐ ๋งค์ฐ ์ค์ํ๋ค๋ ๊ฒ์ ์ผ๋์ ๋์ธ์.
๋ชจ๋ฌ ๋ค์ด์ผ๋ก๊ทธ(modal dialogs)์ ๊ฒฝ์ฐ WAI-ARIA Modal Authoring Practices์ ๋ฐ๋ผ ๋ชจ๋ ๋ชจ๋ฌ ๋ค์ด์ผ๋ก๊ทธ(modal dialogs)์ ์ํธ์์ฉํ ์ ์๋์ง ํ์ธํด์ฃผ์ธ์.
Portal์ ํตํ ์ด๋ฒคํธ ๋ฒ๋ธ๋ง
portal์ด DOM ํธ๋ฆฌ์ ์ด๋์๋ ์กด์ฌํ ์ ์๋ค ํ๋๋ผ๋ ๋ชจ๋ ๋ค๋ฅธ ๋ฉด์์ ์ผ๋ฐ์ ์ธ React ์์์ฒ๋ผ ๋์ํฉ๋๋ค. context์ ๊ฐ์ ๊ธฐ๋ฅ์ ์์์ด portal์ด๋ ์ง ์๋๋ ์ง ์๊ด์์ด ์ ํํ๊ฒ ๊ฐ๊ฒ ๋์ํฉ๋๋ค. ์ด๋ DOM ํธ๋ฆฌ์์์ ์์น์ ์๊ด์์ด portal์ ์ฌ์ ํ React ํธ๋ฆฌ์ ์กด์ฌํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ด๊ฒ์๋ ์ด๋ฒคํธ ๋ฒ๋ธ๋ง๋ ํฌํจ๋์ด ์์ต๋๋ค. portal ๋ด๋ถ์์ ๋ฐ์ํ ์ด๋ฒคํธ๋ React ํธ๋ฆฌ์ ํฌํจ๋ ์์๋ก ์ ํ๋ ๊ฒ์ ๋๋ค. DOM ํธ๋ฆฌ์์๋ ๊ทธ ์์๊ฐ ์๋๋ผ ํ๋๋ผ๋ ๋ง์ ๋๋ค. ๋ค์์ HTML ๊ตฌ์กฐ๋ฅผ ๊ฐ์ ํด ๋ด ์๋ค.
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
#app-root
์์ ์๋ Parent
์ปดํฌ๋ํธ๋ ํ์ ๋
ธ๋์ธ #modal-root
์์ ์ปดํฌ๋ํธ์์ ์ ํ๋ ์ด๋ฒคํธ๊ฐ ํฌ์ฐฉ๋์ง ์์์ ๊ฒฝ์ฐ ๊ทธ๊ฒ์ ํฌ์ฐฉํ ์ ์์ต๋๋ค.
// ์ฌ๊ธฐ ์ด ๋ ์ปจํ
์ด๋๋ DOM์์ ํ์ ๊ด๊ณ์
๋๋ค.
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
// Portal ์๋ฆฌ๋จผํธ๋ Modal์ ์์์ด ๋ง์ดํธ๋ ํ DOM ํธ๋ฆฌ์ ์ฝ์
๋ฉ๋๋ค.
// ์์ปจ๋, ์์์ ์ด๋์๋ ์ฐ๊ฒฐ๋์ง ์์ DOM ๋
ธ๋๋ก ๋ง์ดํธ๋ฉ๋๋ค.
// ์์ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋ ๋ ๊ทธ๊ฒ์ ์ฆ์ DOM ํธ๋ฆฌ์ ์ฐ๊ฒฐํด์ผ๋ง ํ๋ค๋ฉด,
// ์๋ฅผ ๋ค์ด, DOM ๋
ธ๋๋ฅผ ๊ณ์ฐํ๋ค๋ ์ง ์์ ๋
ธ๋์์ 'autoFocus'๋ฅผ ์ฌ์ฉํ๋ค๋ ์ง ํ๋ ๊ฒฝ์ฐ์,
// Modal์ state๋ฅผ ์ถ๊ฐํ๊ณ Modal์ด DOM ํธ๋ฆฌ์ ์ฝ์
๋์ด ์์ ๋๋ง ์์์ ๋ ๋๋งํด์ฃผ์ธ์.
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el
); }}class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {clicks: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// ์ด๊ฒ์ Child์ ์๋ ๋ฒํผ์ด ํด๋ฆญ ๋์์ ๋ ๋ฐ์ํ๊ณ Parent์ state๋ฅผ ๊ฐฑ์ ํฉ๋๋ค.
// ๋น๋ก ๋ฒํผ์ด DOM ์์์ ์ง๊ณ ์์์ด ์๋๋ผ๊ณ ํ๋๋ผ๋ ๋ง์
๋๋ค.
this.setState(state => ({ clicks: state.clicks + 1 })); } render() { return ( <div onClick={this.handleClick}> <p>Number of clicks: {this.state.clicks}</p>
<p>
Open up the browser DevTools
to observe that the button is not a child of the div
with the onClick handler.
</p>
<Modal>
<Child />
</Modal>
</div>
); }}
function Child() {
// ์ด ๋ฒํผ์์์ ํด๋ฆญ ์ด๋ฒคํธ๋ ๋ถ๋ชจ๋ก ๋ฒ๋ธ๋ง๋ฉ๋๋ค.
// ์๋ํ๋ฉด 'onClick' ์์ฑ์ด ์ ์๋์ง ์์๊ธฐ ๋๋ฌธ์
๋๋ค.
return (
<div className="modal">
<button>Click</button> </div> );
}
const root = ReactDOM.createRoot(appRoot);
root.render(<Parent />);
portal์์ ๋ฒ๋ธ๋ง๋ ์ด๋ฒคํธ๋ฅผ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ํฌ์ฐฉํ๋ค๋ ๊ฒ์ ๋ณธ์ง์ ์ผ๋ก portal์ ์์กดํ์ง ์๋ ์กฐ๊ธ ๋ ์ ์ฐํ ์ถ์ํ ๊ฐ๋ฐ์ด ๊ฐ๋ฅํจ์ ๋ํ๋
๋๋ค.
์๋ฅผ ๋ค์ด, <Modal />
์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ ๋ ๋ถ๋ชจ๋ ๊ทธ๊ฒ์ด portal์ ์ฌ์ฉํ๋์ง์ ๊ด๊ณ์์ด <Modal />
์ ์ด๋ฒคํธ๋ฅผ ํฌ์ฐฉํ ์ ์์ต๋๋ค.