본문 바로가기
Study/react.js

[React] React Portal 기술을 이용하여 Modal을 만드는 방법(실습파일 하단 첨부)

by 박히밍 2023. 2. 6.
반응형

 

[React] React Portal 기술을 이용하여 Modal을 만드는 방법(실습파일 하단 첨부)

 

 

[React] React Portal 기술을 이용하여 Modal을 만드는 방법(실습파일 하단 첨부)

요즘 리액트 고급 기술들을 격파하고 있습니다. 그 중에 하나인 React Portal을 활용하여, Modal을 만들어봤습니다.

 

 

 


 

 

자..! 가라! 부모 요소 너머로..!

 

 

Portal?

Portal은 리액트 프로젝트에서 컴포넌트를 렌더링하게 될 때, UI 를 어디에 렌더링 시킬지 DOM 을 사전에 선택하여 부모 컴포넌트의 바깥에 렌더링 할 수 있게 해주는 기능입니다.

 

 

 

 


 

 

 

초기 세팅

//App.js
import React, { useState } from "react";
import "./App.css";
import { MyModal } from "./MyModal";
import { ModalPortal } from "./ModalPortal";

const App = () => {
  return (
    <div className="App">
      <h1>안녕하세요 리액트!</h1>
    </div>
  );
};

export default App;
/* App.css */
.App {
  text-align: center;
  color: #61dafb;
}
<!-- index.html -->
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
    <div id="modal"></div>
  </body>
</html>

App.js / App.css 위와 같이 작성하고, index.html 안에는 <div id="root"></div> 밑에 <div id="modal"></div>를 추가해준다.

 

 

 

 

Portal 생성

//ModalPortal.js를 생성한다.

import ReactDOM from "react-dom";

export const ModalPortal = ({ children }) => {
  const el = document.getElementById("modal");
  return ReactDOM.createPortal(children, el);
};

추후에 이 ModalPortal컴포넌트를 사용하면 우리가 원하는 JSX 를 id="modal"을 가진 DOM 엘리먼트를 탐색한 뒤 렌더링을 할 수 있게된다.

 

 

 

 

 

Modal Component 생성

//MyModal.js
import React from "react";
import "./MyModal.css";

export const MyModal = () => {
  return (
    <div className="MyModal">
      <div className="Mask"></div>
      <div className="Modal-body">
        <div className="content">
          <h3>모달 타이틀</h3>
          <p>모달 텍스트 입니다.</p>
          <button>닫기</button>
        </div>
      </div>
    </div>
  );
};
/* MyModal.css */
.Mask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
}
.Modal-body {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.Modal-body .content {
  background: white;
  padding: 1rem;
  width: 400px;
  height: auto;
}

MyModal 컴포넌트를 만들고나서, 한번 App.js에서 불러와보자.

 

 

 

import React, { useState } from "react";
import "./App.css";
import { MyModal } from "./MyModal";

const App = () => {

  return (
    <div className="App">
      <h1>안녕하세요 리액트!</h1>
      <MyModal />
    </div>
  );
};

export default App;

스타일이 전부 먹힘

결과화면

MyModal이 App.js 안에 생성 되었다.

App.css 에서 지정해주었던 스타일을 상속받아서 모달의 텍스트 컬러까지 함께 바뀐 것을 확인할 수 있다. 하지만 모달의 경우 자신만의 스타일을 갖고 있기 때문에, 부모 요소의 스타일과 상태를 물려받으면 안된다. 그래서 사용해야하는 것이 Portal 기능 이다.

 

 


 

 

 

 

Portal 사용

MyModal component를 아까 우리가 만들었던 ModalPortal로 감싸주어보자.

import React, { useState } from "react";
import "./App.css";
import { MyModal } from "./MyModal";
import { ModalPortal } from "./ModalPortal";

const App = () => {
  return (
    <div className="App">
      <h1>안녕하세요 리액트!</h1>
      <button>모달열기</button>
      <ModalPortal>
        <MyModal />
      </ModalPortal>
    </div>
  );
};

export default App;

전역 스타일이 사라짐
도큐먼트 구조

결과화면

MyModal 컴포넌트가 root 컴포넌트 바깥인 body 태그(부모)의 자식요소로 생성된 것을 확인할 수 있고,

따라서 App.css의 스타일을 상속받지 않는 것으로 확인할 수 있다.

 

 

 

 

 

이벤트 추가(Modal Open / Close Click Event)

//App.js

import React, { useState } from "react";
import "./App.css";
import { MyModal } from "./MyModal";
import { ModalPortal } from "./ModalPortal";

const App = () => {
  const [isOpen, setIsOpen] = useState(false);
  function handleOpenModal() {
    console.log(isOpen);
    setIsOpen(true);
  }
  function handleCloseModal() {
    setIsOpen(false);
  }
  return (
    <div className="App">
      <h1>안녕하세요 리액트!</h1>
      <button onClick={handleOpenModal}>모달열기</button>
      {isOpen && (
        <ModalPortal>
          <MyModal onClose={handleCloseModal} />
        </ModalPortal>
      )}
    </div>
  );
};

export default App;

isOpen State의 기본 상태값을 false로 지정한다.

그리고 handleOpenModal()과 handleCloseModal()에 각각 Click 이벤트 작동 시 setIsOpen()를 통해 true / false로 상태값이 변경되도록 지정해준다.

 

 

<button *onClick*={handleOpenModal}>모달열기</button>

모달열기 버튼에는 onClick 이벤트를지정하고 handleOpenModal 함수를 클릭 시 호출하도록 하며

 

 

<*MyModal* *onClose*={handleCloseModal} />

MyModal 컴포넌트에는 onClose라는 props를 전달하는데 handleCloseModal 함수를 전달한다.

 

 

//MyModal.js

import React from "react";
import "./MyModal.css";

export const MyModal = ({ onClose }) => {
  return (
    <div className="MyModal">
      <div className="Mask" onClick={onClose}></div>
      <div className="Modal-body">
        <div className="content">
          <h3>모달 타이틀</h3>
          <p>모달 텍스트 입니다.</p>
          <button onClick={onClose}>닫기</button>
        </div>
      </div>
    </div>
  );
};

전달받은 onClose props를 onClick이벤트로 호출할 수 있게 하는데,

<button onClick={onClose}>닫기</button> 에만 클릭이벤트를 지정할 것이 아니라

 

 

//background mask 클릭 시 모달이 닫히도록 구현
<div className="Mask" onClick={onClose}></div>

Mask div에도 onClick={onClose} 를 지정하여 백그라운드 mask(반투명 검은레이어)를 클릭해도 모달이 닫히도록 구현한다.

Mask div를 클릭하면 닫히도록 구현이 되어있기 때문에

Modal-body나 content를 클릭해도 모달은 닫히지 않는다.

 

 

 


 

 

 

실습 코드 완성본

 

react-portal.zip
0.28MB

터미널 창에 npm install 하신 뒤, npm start 돌려보세요~

 

 

참고자료

https://velog.io/@velopert/react-portals

 

리액트 - Portals 를 통한 부모 컴포넌트의 외부 DOM 에 컴포넌트 렌더링하기

Portals 는 리액트 프로젝트에서 컴포넌트를 렌더링하게 될 때, UI 를 어디에 렌더링 시킬지 DOM 을 사전에 선택하여 부모 컴포넌트의 바깥에 렌더링 할 수 있게 해주는 기능입니다.

velog.io

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

 

Portals – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

 

 

반응형