아래 블로그 내용 참고하여 만들었음.
https://cocoder16.tistory.com/84
https://mui.com/material-ui/react-modal/
방법 1. 직접 모달창 컴포넌트 만들기
먼저 모달창으로 사용할 UI 컴포넌트를 만들기 위해 Modal.jsx 파일을 생성합니다.
* Modal.jsx
function Modal() {
return (
<div>
Modal
</div>
);
}
이 모달 컴포넌트를 렌더링할 부모 컴포넌트에서는 모달이 열려있는지, 닫혀있는지에 대한 상태를 관리하고 그 상태 값을 모달 컴포넌트에 전달해야 합니다. 모달 컴포넌트에 부모로부터 받을 수 있는 props 코드를 추가하겠습니다.
* Modal.jsx
function Modal({ isOpen }) {
return (
<div style={{ display: isOpen ? "block" : "none" }}>
Modal
</div>
);
}
CSS의 display 속성을 이용하였습니다. 결과적으로 isOpen이 true이면 display의 값은 "block"이 되어 사용자의 화면에 모달창이 나타나고 isOpen이 false이면 사라집니다.
다음으로 모달창을 닫는 버튼을 추가해야 합니다. 버튼을 클릭할 때 isOpen값을 false로 변경해야 합니다. 그런데 isOpen은 모달의 부모 컴포넌트에서 관리하는 상태값이므로, 이것을 변경하는 행위를 담당하는 메서드는 부모 컴포넌트에서 정의해야 합니다. 따라서 이 메서드도 props로 받도록 합니다. 메서드명을 closeModal이라고 하겠습니다.
* Modal.jsx
function Modal({ isOpen, closeModal }) {
return (
<div style={{ display: isOpen ? "block" : "none" }}>
<p>Modal</p>
<button onClick={closeModal}>Close</button>
</div>
);
}
export default Modal;
외부에서 사용할 수 있게 export 코드도 추가하였습니다.
외부에서 모달 컴포넌트를 렌더링하는 방법은 다음과 같습니다.
* App.jsx
import { useState } from "react";
import Modal from "./Modal";
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal isOpen={isModalOpen} closeModal={closeModal} />
</div>
);
}
export default App;
isOpen 상태와 그 값을 변경할 수 있는 메서드들을 선언하여 UI에 연결하였습니다.
다음으로 모달에 존재해야하는 내용을 렌더링해야 합니다. 모달 컴포넌트를 공용 UI 컴포넌트로 만들어 어디서든 재사용가능하도록 만들기 위해서는 데이터를 외부로부터 받아오도록 하는 것이 좋습니다. 그래서 이것도 props로 받도록 하겠습니다.
* Modal.jsx
function Modal({ isOpen, content, closeModal }) {
return (
<div style={{ display: isOpen ? "block" : "none" }}>
<div>{content}</div>
<button onClick={closeModal}>Close</button>
</div>
);
}
html이나 다른 컴포넌트 등을 렌더링할 수 있게 자유도를 더 높이려면 children을 사용할 수도 있습니다.
* Modal.jsx
function Modal({ isOpen, children, closeModal }) {
return (
<div style={{ display: isOpen ? "block" : "none" }}>
<div>{children}</div>
<button onClick={closeModal}>Close</button>
</div>
);
}
content props를 쓴 컴포넌트와 children을 사용하는 컴포넌트는 부모 컴포넌트에서 각각 다음과 같은 사용법의 차이가 있습니다.
* content를 사용한 경우
import { useState } from "react";
import Modal from "./Modal";
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal isOpen={isModalOpen} content="Hi. It's me." closeModal={closeModal} />
</div>
);
}
* children을 사용한 경우
import { useState } from "react";
import Modal from "./Modal";
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal isOpen={isModalOpen} closeModal={closeModal}>
<h2>hi</h2>
<p>it's me</p>
</Modal>
</div>
);
}
children을 사용한 방식은 Modal 마크업 안에 마크업으로 내용을 작성할 수 있어 자유도가 더 높습니다. 해당 내용은 Modal.jsx에서 {children}이라고 쓴 부분에서 렌더링됩니다.
검은 백그라운드 위에 모달창을 중앙에 띄우도록 CSS코드를 추가하겠습니다
* Modal.jsx
function Modal({ isOpen, content, closeModal }) {
return (
<div
style={{
display: isOpen ? "block" : "none",
}}
>
<div
style={{
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
backgroundColor: "rgba(0, 0, 0, 0.35)",
}}
></div>
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 800,
maxWidth: "100%",
maxHeight: "90%",
overflowY: "auto",
backgroundColor: "white",
}}
>
<div>{content}</div>
<button onClick={closeModal}>Close</button>
</div>
</div>
);
}
방법 2. UI 라이브러리 사용하기 - Material UI (MUI)
Material UI (MUI)를 사용하는 방법은 편의성이 좋은 세련된 UI를 그대로 사용할 수 있어 편리합니다.
MUI를 yarn으로 설치하는 방법입니다. 터미널에서 입력합니다. 공식 홈페이지 - 설치법
yarn add @mui/material @emotion/react @emotion/styled
MUI가 제공하는 Modal을 import 하는 방법입니다.
import Modal from "@mui/material/Modal";
다음과 같이 바로 사용할 수 있습니다. 다른 MUI의 사용법들은 설명을 생략하겠습니다. MUI에 대해 더 자세한 설명은 이 포스트나 MUI 공식 홈페이지를 참고해 주세요.
* App.jsx
import { useState } from "react";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Modal from "@mui/material/Modal";
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<div>
<Button onClick={openModal}>Open modal</Button>
<Modal
open={isModalOpen}
onClose={closeModal}
>
<Box>
<Typography variant="h6" component="h2">
hi
</Typography>
<Typography sx={{ mt: 2 }}>
it's me
</Typography>
</Box>
</Modal>
</div>
);
}
export default App;
Modal은 children을 사용하여 자유롭게 내용물을 렌더링할 수 있습니다. 위 예시 코드에서는 MUI가 제공하는 다른 UI들로 내용물을 채워봤습니다. open과 onClose는 MUI에서 제공하는 api이므로 이것을 사용합니다.
모달을 공용 컴포넌트로 만들어 사용하려면 직접 만든 방식에서 한 것처럼 컴포넌트를 따로 분리합니다.
* CustomModal.jsx
import Modal from "@mui/material/Modal";
function CustomModal({ isOpen, closeModal, children }) {
return (
<Modal open={isOpen} onClose={closeModal}>
{children}
</Modal>
);
}
export default CustomModal;
공용 디자인을 입히려면 공용 컴포넌트 내에서 디자인합니다.
* CustomModal.jsx
import Modal from "@mui/material/Modal";
import Paper from "@mui/material/Paper";
function CustomModal({ isOpen, closeModal, children }) {
return (
<Modal open={isOpen} onClose={closeModal}>
<Paper
elevation={2}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 800,
maxWidth: "100%",
maxHeight: "90%",
overflowY: "auto",
}}
>
{children}
</Paper>
</Modal>
);
}
export default CustomModal;
부모 컴포넌트에서는 mui의 Modal 모듈을 직접 임포트하는 것이 아니라 이 컴포넌트를 임포트하여 사용하면 됩니다.
* App.jsx
import { useState } from "react";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import CustomModal from "./CustomModal";
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<div>
<Button onClick={openModal}>Open modal</Button>
<CustomModal
isOpen={isModalOpen}
closeModal={closeModal}
>
<Box>
<Typography variant="h6" component="h2">
hi
</Typography>
<Typography sx={{ mt: 2 }}>
it's me
</Typography>
</Box>
</CustomModal>
</div>
);
}
export default App;
+ 추가 기능
* 백그라운드 클릭시 모달창이 닫히게 하려면?
mui Modal 컴포넌트에 있는 onBackdropClick props사용
<Modal open={open} onBackdropClick={onClose}> //onClose는 닫히는 함수
* 활용예시(swiper js 플러그인 적용/슬라이더 이미지 클릭시 크게 보이는 MUI 모달창 뜨게 만들기)
import { useState } from 'react';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css/pagination';
import 'swiper/css';
...
import CustomModal from '@components/modal/CustomModal';
import Box from '@mui/material/Box';
export default function SwiperImg() {
const items = [
{ url:imgSwiper1 }, //imgSwiper1은 위에 import한 이미지 파일 변수
{ url:imgSwiper2 },
{ url:imgSwiper3 },
];
const [isModalOpen, setIsModalOpen] = useState(false); //모달 오픈
const [selectedImage, setSelectedImage] = useState(''); //swiper img클릭시 크게보이는 img
const openModal = (imageUrl: string) => {
setIsModalOpen(true);
setSelectedImage(imageUrl); //swiper img클릭시 크게보이는 img
document.documentElement.style.overflow = 'hidden'; //body 스크롤 막기
};
const closeModal = () => {
setIsModalOpen(false);
document.documentElement.style.overflow = 'auto'; //body 스크롤 풀기
};
return (
<div className='swiperWrap'>
<Swiper slidesPerView='auto' className='mySwiper'>
{items.map((item, idx) => {
return (
<SwiperSlide key={idx}>
<div
style={{
background: `url(${item.url}) no-repeat center/cover `,
backgroundSize: 'cover',
width: 'auto',
height: '100%',
}}
onClick={() => openModal(item.url)}
/>
</SwiperSlide>
);
})}
</Swiper>
<CustomModal open={isModalOpen} onClose={closeModal}>
<Box>
<div
style={{
background: `url(${selectedImage}) no-repeat center/cover `,
}}
className='modalBox'
/>
</Box>
</CustomModal>
</div>
);
}
'React' 카테고리의 다른 글
[React] JS/ 타임스탬프(Date) 날짜 파싱하기(유닉스타임=타임스탬프 를 현재시간으로 변환) (0) | 2023.02.28 |
---|---|
[React] Emotion CSS(인라인) 사용하려면 주석을 이용(CRA) (0) | 2023.02.24 |
[React] MUI (Material-UI) CSS 사용하기 (0) | 2023.02.21 |
[React] useEffect()의 Callback으로 async 함수를 쓰지 않는 이유 (0) | 2023.02.20 |
[React] React Bootstrap 사용하기 (0) | 2023.02.20 |