[React] 모달: Portals

summereuna🐥·2023년 4월 17일
0

React JS

목록 보기
42/69

모달은 기본적으로 페이지 위에 표시되는 오버레이로, 전체 페이지에 대한 오버레이이기 때문에 다른 모든 요소들 위에 있어야 한다.

🌀 의미적, 구조적으로 문제가 있는 모달: Real Dom에서 모달이 다른 HTML 코드 안에 중첩되어 있는 경우

  • 아래 예시 처럼 Real Dom에서 모달이 다른 HTML 코드 안에 중첩되어 있는 경우, 기술적으로는 스타일링(css) 덕분에 작동하지만, semantically(의미적)관점이나 clean HTML structure(간결한 html구조를 갖추었는지)의 관점에서 보면 좋은 코드가 아니다.
  • Real DOM에서 모달이 html 각 요소들 위에 위치한 것이 아니라 요소들 깊숙히 위치해 있는기 때문에, 특히 스크린 리더기는 모달이 오버레이되어 있는 컴포넌트인지 인식할 수 없다.
    • 사이드 드로어, 다이얼로그 같이 오버레이되는 모든 종류의 컴포넌트에 이런 일이 발생할 수 있기 때문에 실제 돔에서 오버레이되는 컴포넌트의 위치는 중요하다.

✅ 의미적, 구조적으로 제대로 된 위치에 있는 모달: Real Dom에서 모달이 다른 HTML 코드와 중첩되지 않고 다른 코드 보다 위에 있는 경우

  • react-dom 라이브러리에서 제공하는 Portal을 사용하면 리액트의 구조는 유지하면서도, Real DOM에서의 모달의 위치를 적절한 위치로 이동시킬 수 있다. 그러면 모달이 의미적, 구조적으로 적절한 위치에 렌더링 된다.

✅ Portal


react-dom 라이브러리에 있는 portal을 사용해 보자!

📚 portal에 필요한 2가지

1. 컴포넌트를 이동시킬 장소

모달을 root 요소 위로, 즉 body 바로 아래 요소로 렌더되게 해보자.

  • body 직계자손으로 root div 위에 backdrop-root, overlay-root루트를 만들어서 여러 다른 종류의 컴포넌트를 해당 루트로 포털되도록 한다.

📍 /public/index.html

<body>
  <div id="backdrop-root"></div>
  <div id="overlay-root"></div>
  <div id="root"></div>
</body>
  • backdrop-root 에는 오버레이될 때 배경 짙어지는 효과나는 컴포넌트 포털되게
  • overlay-root 에는 모든 오버레이 컴포넌트(모달, 사이드 드로어 등)가 포털되게

2. 컴포넌트에게 그 곳에 포털을 가져야 한다고 알리기

  1. import ReactDOM from "react-dom";
    먼저 react-dom 라이브러리에 있는 portals을 사용하기 위해 react-dom을 임포트 한다.

  2. JSX를 반환하는 컴포넌트 각각 만들기

  1. Portal사용하여 Real DOM의 다른 위치로 ReactDOM JSX를 보내기
    {ReactDOM.createPortal(children, containder)}
  • createPortal()은 2가지 인자를 가진다.

    • children
      : 렌더링되어야 하는 리액트 노드로 2.에서 만든 컴포넌트
      (JSX 사용: <컴포넌트 props />)
    • container
      : 요소가 렌더링되어야 하는 real DOM의 엘리먼트를 포인팅
      (DOM API 사용: document.getElementById(""))
  • 즉, {ReactDOM.createPortal(보낼 리액트돔-JSX, 보내질 장소인 리얼돔 위치-DOM API)}이라고 생각하면 된다.

📍 /src/components/UI/ErrorModal.js

import React from "react";

// 1. react-dom 라이브러리에 있는 portals을 사용하기 위해 react-dom을 임포트 한다.
import ReactDOM from "react-dom";

import Card from "./Card";
import Button from "./Button";
import classes from "./ErrorModal.module.css";

//2-1. backdrop 클래스 가진 JSX 반환하는 컴포넌트
//     -> backdrop-root로 포털되게하기
const Backdrop = (props) => {
  return <div className={classes.backdrop} onClick={props.onConfirm} />;
};

//2-2. modal 클래스 가진 JSX 반환하는 컴포넌트
//     -> overlay-root로 포털되게하기
const ModalOverlay = (props) => {
  return (
    <Card className={classes.modal}>
      <header className={classes.header}>
        <h2>{props.title}</h2>
      </header>
      <div className={classes.content}>
        <p>{props.message}</p>
      </div>
      <footer className={classes.actions}>
        <Button onClick={props.onConfirm}>Okay</Button>
      </footer>
    </Card>
  );
};

// 3. Portal사용하여 Real DOM의 다른 위치로 ReactDOM JSX를 보내기
//{ReactDOM.createPortal(리액트돔-JSX, 리얼돔 위치-DOM API)}
const ErrorModal = (props) => {
  return (
    <>
      {ReactDOM.createPortal(
        <Backdrop onConfirm={props.onConfirm} />,
        document.getElementById("backdrop-root")
      )}

      {ReactDOM.createPortal(
        <ModalOverlay
          title={props.title}
          message={props.message}
          onConfirm={props.onConfirm}
        />,
        document.getElementById("overlay-root")
      )}
    </>
  );
};

export default ErrorModal;

📚 +a

포털의 핵심은 Real DOM에 렌더링될 위치를 설정하고, JSX 코드 안에서 렌더링된 HTML 내용을 그 위치로 옮기는 것이다. 즉 이동시키는 것이 핵심이다.

Portal과 비슷한 예시: App.js에서 root 요소를 가져와 render()메소드로 렌더링

  • 📍 /src/index.js에서도 DOM API인 getElementById를 사용하여 선택한 root 요소로 App 컴포넌트를 render()메소드로 렌더링했다.

위 예시와 Portal의 차이점

  • Portal도 같은 원리이긴 하나, Portal에서는 요소를 렌더링하는 게 아니라 리액트에 의해 이미 렌더링된 기존 애플리케이션 내부로 포털시킨다.
    즉 렌더링하려는 HTML 내용, 즉 JSX(<Backdrop />, <ModalOverlay />)를 다른 장소("backdrop-root"와 "overlay-root")로 이동시킨다.

참고
모달과 팝업

profile
Always have hope🍀 & constant passion🔥

0개의 댓글