๋ฌธ์ ์
์๊ตฌ์ฌํญ
์์ฒ๋ผ 6๊ฐ์ ๋ชจ๋ฌ์ ๋ง๋ค์ด์ผ ํ๋ค.
๋ด๊ฐ ๋ชจ๋ฌ์ ๋ง๋ ๋ฐฉ๋ฒ
export const Modal = ({
modalRef,
openModal,
handleModalClose,
children,
}: Props) => {
if (!openModal) {
return <></>;
}
return (
<>
<div id={styles.backdrop}>.</div>
<div className={styles.container}>
<div className={styles.modal} ref={modalRef}>
<button className={styles.closeBtn} onClick={handleModalClose}>
<ImCross />
</button>
{children}
</div>
</div>
</>
);
};
์์ modal์ ์ฌ๋ฌ ๋ชจ๋ฌ์ ๊ณตํต ๋ถ๋ชจ ์ปดํฌ๋ํธ ์ญํ ์ ํ๋ฉฐ, ๋ชจ๋ฌ์ ๊ณตํต์ ์ธ ๊ธฐ๋ฅ๋ค์ ๋ฃ์ด๋์๋ค.
๋ชจ๋ฌ์ ์ฌ๋ ํจ์(openModal์ true๋ก ๋ฐ๊พธ๋ ํจ์)์ ๊ฒฝ์ฐ๋ ๋ชจ๋ฌ์ ์ฌ์ฉํ๋ ์ชฝ์์ ๋ง๋ค์ด์ค์ผ ํ๋ค. ๋ชจ๋ฌ์ ๋ซ๋ ํจ์ ๋ํ ๋ง์ฐฌ๊ฐ์ง์๊ณ , ์ด๊ฒ๋ค์ ๋ชจ๋ฌ์ ์ฌ์ฉํ๋ ์ชฝ์์ ์ผ์ผ์ด state๋ก ์์ฑํ๋ ๊ฒ์ ๋นํจ์จ์ ์ด์๊ธฐ ๋๋ฌธ์ ์ปค์คํ ํ ์ ๋ง๋ค์๋ค.
import { useRef, useState } from "react";
import useHandleOutsideClick from "./useHandleOutsideClick";
export const useModal = () => {
const modalRef = useRef(null);
const [openModal, setOpenModal] = useState(false);
const handleModalOpen = () => {
setOpenModal(true);
};
const handleModalClose = () => {
setOpenModal(false);
};
useHandleOutsideClick(modalRef, handleModalClose);
return {
openModal,
modalRef,
handleModalOpen,
handleModalClose,
};
};
์ปค์คํ ํ ์ ์์ ๊ฐ์ด ๋ง๋ค์ด์ฃผ์๋ค.
const { openModal, modalRef, handleModalClose, handleModalOpen } = useModal();
//์๋ต...
return(
<ModalPortal>
<DeleteModal
ref={modalRef}
openModal={openModal}
handleModalClose={handleModalClose}
title="ํด๋ ์ญ์ "
description={folderName}
/>
</ModalPortal>
)
๋ชจ๋ฌ์ ์ฌ์ฉํ๋ ๊ณณ ( ๋ชจ๋ฌ์ ๋์ฐ๋ ๋ฒํผ์ด ์๋ ๊ณณ)์์ useModal์ ํตํด ๊ฐํธํ๊ฒ ๋ชจ๋ฌ์ ์ ์ดํ ์ ์๋ค๋ ์ฅ์ ์ด ์์๋ค.
๊ฐ์ ์
๋ค๋ง, ๋ฌธ์ ๋ ๋์ด์ผํ ๋ชจ๋ฌ์ด ์ฌ๋ฌ๊ฐ์ผ ๋ ์์ ๊ฐ์ด ์ฝ๋๊ฐ ๋๋ฌด ๊ธธ์ด์ง๋ค๋ ๊ฒ์ด๋ค. ์ ์ฝ๋๋ ๋ชจ๋ฌ์ ๊ฐ์๊ฐ 3๊ฐ ๋ฟ์ด์ง๋ง ๋ ๋ง์์ง๊ฒ ๋๋ค๋ฉด ์์ ๊ฐ์ด ์ ๋ ๊ฒ์ด ๋ฐ๋์งํ ๊น?
๋ํ, Modal์ด๋ผ๋ ๊ณตํต ๋ถ๋ชจ์ปดํฌ๋ํธ๊ฐ ์๋๋ฐ๋ ModalPortal์ ์ ๋ ๊ฒ ๋ฐ๋ก ์ง์ ํด ์ฃผ์๋ค๋ ๊ฒ์ด๋ค. ๋จ์ ์ฝ๋๋ฅผ ๋ณด๊ณ ๋ฌด์กฐ๊ฑด ์ ์ผ๋ก ์์ฉํ ํ์ด๋ค. ์ด๋ฐ ๋ฒ๋ฆ์ ๊ณ ์ณ์ผ ๊ฒ ๋ค.
๋ฆฌํฉํ ๋ง
์ฒซ๋ฒ์งธ ๊ฐ์
export const Modal = ({
modalRef,
openModal,
handleModalClose,
children,
}: Props) => {
if (!openModal) {
return <></>;
}
return (
<ModalPortal>
<div id={styles.backdrop}>.</div>
<div className={styles.container}>
<div className={styles.modal} ref={modalRef}>
<button className={styles.closeBtn} onClick={handleModalClose}>
<ImCross />
</button>
{children}
</div>
</div>
</ModalPortal>
);
};
๊ณตํต ๋ชจ๋ฌ ์ปดํฌ๋ํธ์ ModalPortal์ ๋ฃ์ด์ฃผ์๋ค.
๋๋ฒ์งธ ๊ฐ์
์ด ๋ถ๋ถ์ ํ ๋ฒ ํด๊ฒฐํด๋ณด์
์๊ฐํ ๊ฐ์ ๋ฒ
๋ด๊ฐ ์๊ฐํ ๊ฐ์ ๋ฒ์ผ๋ก๋ ์ ์ญ์ ์ผ๋ก ๊ณต์ ๋๋ ๋ชจ๋ฌ์ ์ํ๋ฅผ ๋ง๋ค๊ณ ์ด ๋ชจ๋ฌ์ ์ํ์ ๋ฐ๋ผ์ Modal ์ปดํฌ๋ํธ ๋ด๋ถ์์ ์ฌ๋ฌ๊ฐ์ ์๋ก๋ค๋ฅธ ๋ชจ๋ฌ์ ๋ ๋๋งํ๊ฒ ํ๋ ๊ฒ์ด๋ค. ๋ชจ๋ฌ๋ค์ ๋์์ฃผ๋ ์ปดํฌ๋ํธ์์๋ ๊ทธ์ ์ด ์ํ๋ฅผ ๋ฐ๊ฟ์ฃผ๋ฉด ์ด๋จ๊น?
ํน์ ์ํ์ ๋ฐ๋ผ ์๋ก ๋ค๋ฅธ ๋ชจ๋ฌ ์ปดํฌ๋ํธ ๋ ๋๋งํ์
์์์ ์ธ๊ธํ ๋ฌธ์ ๋ฅผ ํผํ๊ธฐ ์ํด์ ๋ชจ๋ ๋ชจ๋ฌ๋ค์ ๊ณตํต ๋ชจ๋ฌ ์ปดํฌ๋ํธ์์ ๋ชจ๋ฌ ํ์ ์ ๋ฐ๋ผ ์กฐ๊ฑด๋ถ๋ ๋๋ง์ ํ๋ฉด ๋์ง ์์๊น ์๊ฐํ๊ณ ๊ทธ๋ผ ๋ชจ๋ฌ์ ์ฌ์ฉํ๋ ๊ณณ์์๋ ๊ทธ ๋ชจ๋ฌ ํ์ ๊ณผ ๋ชจ๋ฌ์ ํ์ํ ๋ฐ์ดํฐ๋ง ๋ฐ๊ฟ์ฃผ๋ฉด ๋ ๊ฒ ๊ฐ์๋ค. ๋ชจ๋ฌํ์ ๊ณผ ๋ชจ๋ฌ์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ์ญ๊ด๋ฆฌํ๋ฉด ๋๋ ๊ฒ์ด์๋ค.
Context API์ useReducer ์ฌ์ฉ
์๋๋ redux toolkit์ ์ฌ์ฉํด๋ณผ๊น ํ์ง๋ง ์์ง์ ์ต์ํ์ง๋ ์๊ณ , context api ์ฌ์ฉ์ ๋ง์ด ์ํด๋ณด์๊ธฐ ๋๋ฌธ์ context api๋ฅผ ์ฌ์ฉํด๋ณด๊ธฐ๋ก ํ๋ค. ๊ทธ๋ฆฌ๊ณ state๋ฅผ ์ฌ์ฉํ์ง ์๊ณ useReducer๋ฅผ ์ฌ์ฉํ๋๋ฐ ๋จ์ํ ๋ง์ด ์ ์จ๋ด์ ์ฐ์ต์ ํ๊ธฐ ์ํด์์์ง๋ง ๋ค ๋ง๋ค๊ณ ๋๋ state๋๋น useReducer์ ์ฅ์ ์ ์๊ฒ ๋์๋ค.
์ด๋ป๊ฒ ๊ตฌํ ํ๋
const initialState: Type = {
type: "",
isOpen: false,
data: {
title: null,
description: null,
subTitle: null,
folders: null,
folderName: null,
linkUrl: null,
},
};
์ ๊ฐ์ ์ ์ญ์์ ๊ด๋ฆฌํ์๋ค. ์ ๋ฐ์ดํฐ ์์ฒด๋ Modal์์ ๋ฐ์ ์์ฐ๊ธด ํ์ง๋ง Props๋ก ์ ๋ฌ์ ํด์ฃผ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ์ ์ญ๊ด๋ฆฌ๋ฅผ ํ์๋ค.
export const Modal = () => {
const modalData = useContext(ModalContext);
const dispatch = useContext(ModalDispatchContext)!;
if (!modalData.isOpen) {
return <></>;
}
const handleModalRemoveClick = () => {
dispatch({ type: "hideModal" });
};
// ์ดํ ์๋ต...
๋ชจ๋ฌ ์ปดํฌ๋ํธ์์๋ ์ ์ญ ๊ด๋ฆฌํ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ๋ชจ๋ฌ์ ๋ณด์ฌ์ค์ง ๋ง์ง ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ํ๊ฒ ๋๋ค.
<ModalPortal>
<div onClick={handleModalRemoveClick} id={styles.backdrop}></div>
<div className={styles.container}>
<div className={styles.modal}>
<button className={styles.closeBtn} onClick={handleModalRemoveClick}>
<ImCross />
</button>
{renderComponent(modalData)}
</div>
</div>
</ModalPortal>
const renderComponent = (modalData: Type): ReactNode => {
switch (modalData.type) {
case "AddLinkModal":
return (
<AddLinkModal
folders={modalData.data.folders!}
linkUrl={modalData.data.linkUrl!}
/>
);
case "AddModal":
return <AddModal />;
case "DeleteModal":
return (
<DeleteModal
title={modalData.data.title!}
description={modalData.data.description!}
/>
);
case "ModifyModal":
return <ModifyModal folderName={modalData.data.folderName!} />;
case "ShareModal":
return <ShareModal folderName={modalData.data.folderName!} />;
default:
return <></>;
}
};
์๋ ๋ชจ๋ฌ์ ํต์ฌ๋ก์ง์ด๋ค. ์ ์ญ์ผ๋ก ๊ด๋ฆฌ๋๋ ๋ฐ์ดํฐ์ type์ ๋ฐ๊ฟ์ฃผ๋ฉด ๋ฐ๋ type์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ชจ๋ฌ์ ๋ณด์ฌ์ฃผ๋๋ก ํ๋ค. ๋ชจ๋ฌ ์ ๋ง๋ค์ ํ์ํ ๋ฐ์ดํฐ๋ ์ ์ญ์ผ๋ก ๋นผ์ฃผ์๊ณ ๋ชจ๋ฌ์ ๋์ฐ๋ ๊ณณ์์ dispatch์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ฃผ์ด ๊ฐ ๋ชจ๋ฌ์ ๋ฐ๋ผ ์ ์ญ ๋ฐ์ดํฐ๊ฐ ์ ๋ฐ์ดํธ๊ฐ ๋๋๋ก ํ์๋ค
์ด๋ ๊ฒ ํ๋ค๋ณด๋ ๋ชจ๋ฌ์ ๋์ฐ๋ ์ปดํฌ๋ํธ์์๋ ๊ทธ์ dispatch๋ฅผ ์ฌ์ฉํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ์ ๋ง ์ฝ๋๊ฐ ๊น๋ํด์ก๋ค
์ฌ๊ธฐ์ ํ์ ์คํฌ๋ฆฝํธ์ ์ฅ์ ์ ๋ ๋๋ ์ ์์๋ค.
์ด๋ ๊ฒ ํ์ ์ ์ง์ ํด๋์ผ๋ฉด ์๋ํฐ๊ฐ ModalType์ ์ ํํ ์ ์๊ฒ ํด์ฃผ๊ณ , ๋ค๋ฅธ ๊ฐ์ ๋ฃ์์ ๋ ์น์ ํ ์ด๋ค ๊ฐ ๋๋ฌธ์ ์ค๋ฅ๊ฐ ๋ฌ๋์ง ์๋ ค์คฌ๊ธฐ ๋๋ฌธ์ ๋๋ฌด ์ข์๋ค.
reducer
function reducer(state: Type, action: Action) {
switch (action.type) {
case "showModal": {
return {
...state,
isOpen: true,
type: action.payload.modalType,
data: {
...state.data,
...action.payload,
},
};
}
case "hideModal": {
return {
...state,
isOpen: false,
};
}
default: {
return state;
}
}
}
๋๋์
์์ง ์ด๊ณณ์ ๊ณณ ๊ฐ์ ์ ์ด ๋ง์ ์ฝ๋์ด๋ค. ์ฌ๋ฌ ์ฌ๋๋ค์ ๋์์ ๋ฐ์ ์ ๋ง ์์ฑ๋ ๋์ ์ฝ๋๋ก ๊ณ์ ๋ฐ์ ์์ผ๋ณด๊ณ ์ถ๋ค๋ ์์ฌ์ด ์๊ฒผ๋ค. (ํ์ ๋ถ๋ถ์ ๋ง์ด ์๋ด์ผ ๋ ๊ฒ ๊ฐ๋ค / ํ์ค ์ํ๋ ์ฌ๋๋ค์ ์กฐ์ธ์ ๊ตฌํ์~!!)