티스토리 뷰

Web/React

[Nextjs] 모달(Modal) 만들기

HAN_PY 2023. 9. 25. 22:00
반응형

모달(Modal)이란, 원하는 내용을 화면 위에 띄워 표현하는 방식이다. 기본적으로 많은 UI 라이브러리에서 쉽게 제작이 가능하다. 오늘은 Modal을 Component로 만들고 Nextjs/React에 적용하는 법을 알아보자.

 

모달을 만들기전에 알아야할 개념중 첫번째가 Portals라는 개념이다. 우리는 필요한 위치에 Component로 만들어 모달을 넣어줄 것이다. 그리고 추가로, 다른 Component보다 앞에 표시가 되려면 z-index를 사용하여 표현을 한다. 이때, 중요한 것은 부모의 z-index가 작다면, 자식의 z-index가 아무리 크더라도 부모의 z-index를 따르게 된다. 그렇기 때문에 Modal 자체를 종속적으로 Component 내부에 넣으면 다른 컴포넌트보다 앞에 있지 않는 경우가 생기게 된다. 이를 방지하려면, 최상단의 root에 있는 부분에 엘리먼트를 만들어 Javascript로 접근을 해서 모달에 추가해 주면된다.

 

 

React Portals

모달이 z-index가 먹히지 않는다면 우리는 Portals라는 개념을 먼저 알아야한다. 기본적으로 React는 부모 컴포넌트의 DOM 내부에서 렌더링이 일어난다. 

 

const ParentComponent = ({ children }) => {
  return <div>{children}</div>;
}

 

Portals는 부모 컴포넌트의 내부 DOM이 아니라, 미리 지정해둔 DOM에서 렌더링 할 수 있는 기능이다.  조금 더 알아보면, Protal은 이벤트 버블링이 Dom내부에서 가능하다. 이벤트 버블링이란, 중첩된 자식 요소에서 이벤트가 발생하면 부모로 이벤트가 전달된다. 이때, 부모 Dom 밖에서 아래와 같이 만들어도, Dom 트리 위치와 상관 없이 Portal은 React 트리 내부에 존재하기 때문에 React의 가상돔에 따른 이벤트 버블링이 된다.

 

import React from "react";


const Modal = ({ open, onClose, children }) => {
  if (!open) return null;
  return ReactDOM.createPortal(
    <>
      <div style={overlayStyle} />
      <div style={modalStyle}>
        <button onClick={onClose}>모달 닫기</button>
        {children}
      </div>
    </>,
    document.getElementById("portal")
  );
};

 

portal id 값을 최상단에 설정을 했다고 가정해보자. Modal 컴포넌트를 만든 기준으로 부모 컴포넌트가 있다면, DOM 트리와 관련 없이 리엑트 트리에서 상위 컴포넌트라면 이벤트 버블링이 된다. 구조를 살펴보면 아래와 같다.

 

ReactDOM.createPortal(child, container)

 

child는 렌더를 할 수 있는 React 자식요소이고, container는 DOM 엘리먼트이다.

 


Nextjs 모달 적용 / React 모달 적용

 

1. Modal Component 만들기

13버전에 올라오면서 Nextjs 구조가 많이 변경 되었다. Nextjs 12와 13버전의 root 부분이 다르기 때문에 아래를 참고해서 모달을 만들어 보자. 먼저 공통 Modal Compoent 코드는 아래와 같다. 아래의 코드에서는 Scss를 사용하였지만, 기호에 맞게 css는 변경해주면 된다.

 

// Modal.tsx
import React, { ReactNode } from "react";
import ReactDOM from "react-dom";
import styles from "./Modal.module.scss";

interface ModalProps {
  open: boolean;
  onClose: () => void;
  children: ReactNode;
}

const Modal = ({ open, onClose, children }: ModalProps) => {
  if (!open) return null;
  return ReactDOM.createPortal(
    <>
      <div className={styles.overlayStyle} />
      <div className={styles.modalStyle}>
        <button onClick={onClose}>모달 닫기</button>
        {children}
      </div>
    </>,
    document.getElementById("global-modal") as HTMLElement
  );
};

export default Modal;

// Modal.module.scss

.overlayStyle {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  z-index: 5;
}

.modalStyle {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 50px;
  background-color: #ffffff;
  z-index: 5;
}

 

이제 상위 html에 우리가 Modal 컴포넌트에서 적은 id값을 넣은 div element를 만들어 주면 된다. 예시코드에서는 id값을 global-modal로 만들었다.

 

 


2. Root Directory에 Element 만들기

Nextjs 12버전 root Directory

우선 root Html부분의 위치는 page/_app.tsx 부분이다. 아래과 같이 dev element를 넣어주면 된다.

 

import Head from "next/head";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
        <title>Deepfake Detection</title>
      </Head>
        <Component {...pageProps} />
        <div id="global-modal"></div>
    </>
  );
}

 

Nextjs 13버전 root Directory

우선 root Html부분의 위치는 app/layout.tsx 부분이다. 아래와 같이 추가해준다.

 

import ToastProvider from '@/components/toastProvider/ToastProvider'

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({ children }: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <div id="global-modal"></div>
      </body>
    </html>
  )
}

 

react root Directory

리액트의 root Html 부분의 위치는 public/index.html 이다. 아래 처럼 root id 아래에 우리가 만들 모달 id를 넣어주면 된다.

 

<div id="root"></div>
<div id="global-modal"></div>

 


3. JSX 사용하기

id값을 설정을 했으면 컴포넌트 내부에서 사용하면 된다. 간단한 컴포넌트 예시는 아래와 같다.

 

import Modal from "@/components/Modal";

const TestComponent = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);
  return (
  <>
      <button onClick={() => setIsModalOpen(true)}>모달 열기</button>
        <Modal open={isModalOpen} onClose={() => setIsModalOpen(false)}>
          모달 내용
        </Modal>
      </div>

  </>
  )
}

 

 

모달을 구현하는 법은 매우 다양하다. 위의 내용은 기초적인 부분만 만들었기 때문에 필요한 프로젝트에 변형해서 사용해 보자!

 

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함