We want to hear from you!Take our 2021 Community Survey!
Support Ukraine ๐Ÿ‡บ๐Ÿ‡ฆ Help Provide Humanitarian Aid to Ukraine.

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)์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.

CodePen์—์„œ ์‹คํ–‰ํ•˜๊ธฐ

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 />);

CodePen์—์„œ ์‹คํ–‰ํ•˜๊ธฐ

portal์—์„œ ๋ฒ„๋ธ”๋ง๋œ ์ด๋ฒคํŠธ๋ฅผ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ํฌ์ฐฉํ•œ๋‹ค๋Š” ๊ฒƒ์€ ๋ณธ์งˆ์ ์œผ๋กœ portal์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ์กฐ๊ธˆ ๋” ์œ ์—ฐํ•œ ์ถ”์ƒํ™” ๊ฐœ๋ฐœ์ด ๊ฐ€๋Šฅํ•จ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, <Modal /> ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•  ๋•Œ ๋ถ€๋ชจ๋Š” ๊ทธ๊ฒƒ์ด portal์„ ์‚ฌ์šฉํ–ˆ๋Š”์ง€์™€ ๊ด€๊ณ„์—†์ด <Modal />์˜ ์ด๋ฒคํŠธ๋ฅผ ํฌ์ฐฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Is this page useful?Edit this page