๊ด€๋ฆฌ ๋ฉ”๋‰ด

SUIN

[React] ๋…๋ฆฝ์ ์ธ DOM ๋…ธ๋“œ๋กœ ๋ Œ๋”๋งํ•˜๊ธฐ. with React Portal ๋ณธ๋ฌธ

๊ฐœ๋ฐœ์ผ์ง€

[React] ๋…๋ฆฝ์ ์ธ DOM ๋…ธ๋“œ๋กœ ๋ Œ๋”๋งํ•˜๊ธฐ. with React Portal

choi suin 2024. 6. 7. 16:50
728x90
๐Ÿ’กํŽ˜์ด์ง€๋ฅผ ๊ตฌ์„ฑํ•˜๋‹ค ๋ณด๋ฉด ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ์€ ๊ณณ์—์„œ ๋ชจ๋‹ฌ๊ณผ ํŒ์—…์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธด๋‹ค. React์—์„œ ๋ชจ๋‹ฌ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ„๋„๋กœ ๋งŒ๋“ค์–ด ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ ์–ธํ•ด์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์šฐ๋ฆฌ๋Š” React์—์„œ ์ œ๊ณต๋˜๋Š” ๊ธฐ๋Šฅ์ธ React Portal์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ชจ๋‹ฌ์„ ๊ตฌํ˜„ํ•ด์ฃผ์—ˆ๋‹ค.
React Portal์„ ์•Œ์•„๋ณด๊ณ  ์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ์—๋Š” ์–ด๋–ค ์‹์œผ๋กœ Portal์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ตฌํ˜„ํ–ˆ๋Š”์ง€ ์•Œ์•„๋ณด์ž.

React Portal๋ž€?

https://medium.com/@vmslakshmi8000/portals-in-react-b9ef4ba812a4

React Portal์€ React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ DOM ์š”์†Œ๋ฅผ ๋‹ค๋ฅธ ์œ„์น˜์˜ DOM ๋…ธ๋“œ๋กœ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ React๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ๋ถ€๋ชจ ์š”์†Œ ๋‚ด์—์„œ ๋ Œ๋”๋ง ํ•˜์ง€๋งŒ ๋•Œ๋กœ๋Š” ํŠน์ • ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถ€๋ชจ ์š”์†Œ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์œ„์น˜๋กœ ๋ Œ๋”๋งํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธฐ๋Š”๋ฐ, ์ด๋Ÿฐ ๊ฒฝ์šฐ์— React Portal์„ ์‚ฌ์šฉํ•œ๋‹ค.

ํฌํ„ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋…๋ฆฝ์ ์ธ UI ์š”์†Œ๋ฅผ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋‹ฌ, ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด, ํˆดํŒ ๋“ฑ๊ณผ ๊ฐ™์€ ๋ณด์กฐ์ ์ธ UI ์š”์†Œ๋ฅผ ๋ Œ๋”๋งํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

https://ko.legacy.reactjs.org/docs/portals.html

https://react.dev/reference/react-dom/createPortal

 

โ“ ์šฐ๋ฆฌ๋Š” ์™œ Portal์„ ์‚ฌ์šฉํ•˜์—ฌ ์™ธ๋ถ€์— ์กด์žฌํ•˜๋Š” DOM ๋…ธ๋“œ์— ๋ Œ๋”๋ง ํ•˜๋„๋ก ๋งŒ๋“ค์–ด ์ฃผ์–ด์•ผ ํ•˜๋Š”๊ฑธ๊นŒ?

 

React Portal ์‚ฌ์šฉ ์ด์œ 

๋ฆฌ์•กํŠธ๋Š” ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋˜๋ฉด ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋˜๋Š” Tree ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ์ด๋Ÿฐ Tree ๊ตฌ์กฐ๋Š” ์ข…์ข… ๋ถˆํŽธํ•จ์„ ๊ฐ€์ง€๊ฒŒ ๋˜๋Š”๋ฐ ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด DOM ๊ณ„์ธต ๊ตฌ์กฐ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๊ฒŒ ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๋ฆฌ์•กํŠธ ํฌํƒˆ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋…๋ฆฝ์ ์ธ ์œ„์น˜์—์„œ ๋ Œ๋”๋งํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

1. ๋…๋ฆฝ์ ์ธ ์Šคํƒ€์ผ ์ ์šฉ (CSS ์ถฉ๋Œ ๋ฐฉ์ง€)

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ์— ์˜ํ–ฅ์„ ๋ฐ›๊ฑฐ๋‚˜ ์ค„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„์˜ ํ›„ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ Portal์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ถ€๋ชจ์™€ ์ž์‹ ๋ชจ๋‘ ์™ธ๋ถ€์˜ ์Šคํƒ€์ผ๋ง ๊ทœ์น™์— ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๊ณ  ๋…๋ฆฝ์ ์ธ ์Šคํƒ€์ผ๋ง์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ค€๋‹ค.

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ ์˜ํ–ฅ์„ ๋ฐ›๋Š” ์˜ˆ์‹œ

const App = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);

  return (
    <div className="app">
      <h1>Main Content</h1>
      <button onClick={() => setIsModalOpen(true)}>Open Modal</button>
      <Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
    </div>
  );
};
/* App.css */
.app {
  background-color: lightblue;
  padding: 20px;
  text-align: center;
  /* ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ ์Šคํƒ€์ผ ๊ทœ์น™ */
  filter: blur(2px);
}

๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ DOM ํŠธ๋ฆฌ์— ํฌํ•จ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ ์Šคํƒ€์ผ์ธ filter: blur(2px)๊ฐ€ ๋ชจ๋‹ฌ์— ํ•จ๊ป˜ ์ ์šฉ๋˜์–ด ๋ชจ๋‹ฌ์ด ์—ด๋ ธ์„ ๋•Œ, ๋ชจ๋‹ฌ์˜ ๋‚ด์šฉ์ด ํ๋ฆฟํ•˜๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

2. ์ด๋ฒคํŠธ ๋ฒ„๋ธ”๋ง ์ œ์–ด

๋ฆฌ์•กํŠธ ํŠธ๋ฆฌ ๊ตฌ์กฐ์— ๋”ฐ๋ผ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ์š”์†Œ๋ถ€ํ„ฐ ์ตœ์ƒ๋‹จ์˜ ๋ถ€๋ชจ์š”์†Œ๋ฅผ ๋งŒ๋‚ ๋•Œ๊นŒ์ง€ ์ด๋ฒคํŠธ๊ฐ€ ์ „ํŒŒ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๊ฐ€ ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ๋‹ค. ์ด ๊ฒฝ์šฐ Portal์„ ์‚ฌ์šฉํ•˜๋ฉด ๋‚ด๋ถ€์—์„œ ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ๋Š” Portal์„ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ชจ ์š”์†Œ๊นŒ์ง€๋งŒ ๋ฒ„๋ธ”๋ง ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ๋”์šฑ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

React Portal์˜ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

๐Ÿ‘‡๐Ÿป ์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ์—์„œ ์ ์šฉํ•œ ์ฝ”๋“œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์–ด๋–ป๊ฒŒ ํฌํ„ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋‹ฌ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด์ž!

1. ํฌํƒˆ์— ๋Œ€ํ•œ DOM ์š”์†Œ ์ƒ์„ฑ- index.html

๋‹ค๋ฅธ DOM ์š”์†Œ์— ๋Œ€ํ•œ id๋ฅผ ์ง€์ •ํ•˜์—ฌ ํ•ด๋‹น ์š”์†Œ์— ํฌํƒˆ์ด ๋ Œ๋”๋ง๋  ์ˆ˜ ์žˆ๋„๋ก ์ง€์ •

<!DOCTYPE html>
<html>
  <head><title>app</title></head>
  <body>
    <h1>Welcome to my hybrid app</h1>
      <div id="root"></div>
      <div id="modal-root"></div> {/* ํฌํƒˆ์ด ๋ Œ๋”๋ง๋  ์œ„์น˜ */}
    </div>
  </body>
</html>

2. Portal์„ ๋ Œ๋”๋งํ•  ์œ„์น˜ ์„ ์–ธ - ModalPortal.tsx

modal-root๋ผ๋Š” id๋ฅผ ๊ฐ€์ง„ ์™ธ๋ถ€ DOM ์š”์†Œ์— ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•ด ModalPortal ์ปดํฌ๋„ŒํŠธ ์ •์˜

const ModalPortal = ({ children }: TProps) => {
	// ์™ธ๋ถ€ DOM ์š”์†Œ์— ๋ Œ๋”๋งํ•  ์œ„์น˜๋ฅผ ์ง€์ •
  const el = document.getElementById('modal-root') as HTMLElement;
	// createPortal ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋‹ฌ์„ ์™ธ๋ถ€ DOM ์š”์†Œ์— ๋ Œ๋”๋ง
  return ReactDom.createPortal(children, el);
};

createPortal() ์‚ฌ์šฉ๋ฒ•
ReactDom์˜ createPortal ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ Portal ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. child์— ๋„ฃ์–ด๋‘” ์ปดํฌ๋„ŒํŠธ๋Š” ๋ Œ๋”๋ง๋  ๋•Œ ๊ฐ€๊นŒ์šด ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•„๋‹Œ ์šฐ๋ฆฌ๊ฐ€ container์— ์„ค์ •ํ•ด ๋‘” ์ปดํฌ๋„ŒํŠธ์— ๋ Œ๋”๋ง๋œ๋‹ค.

ReactDom.createPortal(child, container)
  • child : ์—˜๋ฆฌ๋จผํŠธ, ๋ฌธ์ž์—ด, ํ˜น์€ fragment ๊ฐ™์€ ์–ด๋–ค ์ข…๋ฅ˜์ด๋“  ๋ Œ๋”๋ง ํ•  ์ˆ˜ ์žˆ๋Š” React์˜ ์ž์‹
  • container : DOM ์—˜๋ฆฌ๋จผํŠธ ์š”์†Œ
  • ReactDom.createPortal(child, container)

3. Portal์„ ์‚ฌ์šฉํ•  ์ „์—ญ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ - GlobalModal.tsx

๋ชจ๋‹ฌ ๋‚ด๋ถ€์— ์‚ฌ์šฉ๋˜๋Š” ๊ณตํ†ต๋œ ์Šคํƒ€์ผ๋ง ๋ฐ•์Šค๋‚˜ ๋ฐฐ๊ฒฝ ๋“ฑ์˜ UI ์š”์†Œ๋ฅผ GlobalModal ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด ์‚ฌ์ „์— ์ •์˜

export default function GlobalModal(props: IModal) {
  return (
    <ModalPortal>
      <t.ModalContainer className="ModalContainer">
        <t.ContentBox className="ContentBox">{props.children}</t.ContentBox>
        <t.ModalBackdrop
          className="ModalBackdrop"
          onClick={(e: React.MouseEvent) => {
            e.preventDefault();
            props.onClose();
          }}
        ></t.ModalBackdrop>
      </t.ModalContainer>
    </ModalPortal>
  );
}

4. ๋ชจ๋‹ฌ์ด ๋ Œ๋”๋ง ๋  ์œ„์น˜ ์ง€์ •

  const optionModal = isModalOpen && (
    <GlobalModal onClose={() => setIsModalOpen(false)}>
      <CartOptionModal onClose={() => setIsModalOpen(false)} />
    </GlobalModal>
  );

๊ฒฐ๊ณผ




์ฐธ๊ณ 

https://velog.io/@enenaa/React-Portal-๊ธฐ์ˆ ์„-์ด์šฉํ•˜์—ฌ-Modal-๋งŒ๋“ค๊ธฐ