세번째 프로젝트 Linkbrary 에서 내가 구현한 부분은 zustand를 이용한 전역 모달
세번째 프로젝트를 마치고
지난 포스팅 두번째 프로젝트 회고 말미에 언급한 세미 프로젝트 회고이다.이제 진짜 내일부터 최종 프로젝트 기간이라 후딱 회고 작성하려고 한다. 코드잇 부트캠프에서 진행하는 정식 프로
memoryelim.tistory.com
💣모달을 왜 전역으로 구현했냐는 질문에
💥 어.? 당연히 전역으로 하는거 아니었나?!
음.. 모달이 여러개라서?
여러 모달이 여러 페이지에서 띄워져서?
첫번째 프로젝트를 제외하고 나머지 프로젝트에서 모달을 전역으로 구현했기에 당연스럽게 여기고 있었다.
개발하는데 있어서 배운건 '당연히' 라는게 없다는 것이다.
뭐든 이유가 있다. 그런데도 나는 또 아무 생각 없이 코드를 작성하고 있었구나 !!
전역 모달이 필요한 이유에 대해 알아보려고 한다.
모달이란?
모달은 사용자가 웹사이트나 앱을 사용할 때 나타나는 대화 상자나 팝업 창으로,
사용자의 주의를 특정 작업이나 정보에 집중시키는 데 사용된다
💥 모달이 필요한 매 페이지마다 반복되는 코드 발생
🙆♀️ 전역 모달을 사용함으로써 얻는 이점
1. 코드 반복을 방지하고, 로직을 분리할 수 있다.
2. 전역 상태를 사용하면 모달의 상태가 특정 컴포넌트에 종속되지 않으므로 불필요한 리렌더링을 줄일 수 있다.
3. props drilling 방지 - 컴포넌트 간 의존성 감소
4. 모달 상태 관리의 중앙 집중화
[내가 구현한 코드] 를 살펴보니 막상 전역 상태를 100% 활용하지 못했다는걸 알게 됐다
일단 프로젝트의 페이지 수가 별로 없다보니 모달을 사용하는 곳도 한 페이지 + 링크 input 컴포넌트 뿐이다
게다가 커스텀 훅을 사용하고는 있지만 상태와 함수를 호출하는 코드와 모달을 렌더링하는 코드를 최상위에서 호출하지 않아 코드가 반복되고 있다
약 2달전에 작성한 코드인데 다시 보니 생경하고,, 이상하고..왜이렇게 했나.. ㅋㅋㅋ
어제까지 이게 내 최선이었다 라곤 하지만 너무 만드는데만 급급해 하면서 구현해놓고 다하고는 리팩토링 안해 하면서 손 놓고 있었다는걸 깨닫는다
//상태와 함수를 호출하는 코드
const { isOpen, openModal } = useModalStore();
..
//모달을 렌더링하는 코드
{ isOpenModal && <Modal/> }
// ModalManager.tsx
// 모달 key - value 매칭
export const ModalType = {
AddFolderModal: "AddFolderModal",
AddModal: "AddModal",
DeleteFolderModal: "DeleteFolderModal",
DeleteLinkModal: "DeleteLinkModal",
EditModal: "EditModal",
SNSModal: "SNSModal",
EditLink: "EditLink",
} as const;
export type ModalKeysType = keyof typeof ModalType;
// 모달 렌더링 컴포넌트
export const Modal = () => {
const { modalType, isOpen, props } = useModalStore(); // 전역 상태 이용
if (!modalType || !isOpen) return null;
switch (modalType) {
case "AddFolderModal":
return <AddFolderModal folderName={props.folderName || ""} />;
case "AddModal":
return <AddModal list={props.list || []} link={props.link || ""} />;
case "DeleteFolderModal":
return (
<DeleteFolderModal
folderId={Number(props.folderId)}
linkCount={Number(props.linkCount)}
/>
);
// 반복되는 패턴 - 길어서 생략
}
};
[리팩토링]
<변경 전>
페이지 or 컴포넌트마다 isOpen 조건부로 컴포넌트를 호출했다
//상태와 함수를 호출하는 코드
const { isOpen, openModal } = useModalStore();
..
//모달을 렌더링하는 코드
{isOpen && <Modal/>}
<변경 후>
next.js page 라우터 버전이기 때문에 최상위 컴포넌트인
_app.tsx 에 <Modal /> 을 추가해주었다.
// _app.tsx
<div className="min-h-screen flex flex-col">
<div>
<Toaster />
<Modal />
</div>
</div>
// useModalStore.tsx
const useModalStore = create<ModalStore>((set) => {
return {
modalType: null,
isOpen: false,
props: {},
openModal: (type: ModalKeysType, props = {}) =>
set({ modalType: type, isOpen: true, props }),
closeModal: () => set({ modalType: null, isOpen: false, props: {} }),
};
});
export default useModalStore;
store에서 정의할때 openModal과 closeModal에서 이미 isOpen 을 set 해주고 있기 때문에
굳이 조건부로 isOpen && <Modal /> 하고 붙여줄 필요 없이 <Modal /> 만 추가해준다
모달 호출이 필요한 곳에서는 openModal 에 모달 type과 필요한 prop 만 넘겨주면 된다
<Modal /> 컴포넌트 import는 불필요하다 (상위 _app.tsx에서 렌더링하므로 )
// 이벤트 핸들러에서 모달 호출
const handleModalOpen = (
type: "EditLink" | "DeleteLinkModal",
link: string,
linkId: number
) => {
openModal(type, { link, linkId });
};
모달 관련해서 리팩토링한 분량이 많지는 않지만 그래도 조금이나마 더 좋은 코드가 되었다는 생각에 뿌듯하다 ^- ^
뭣모르고 쓴 코드였지만 포스팅을 하면서 조금씩 내것이 되어가는 것 같다
'개발 공부 일지 > React' 카테고리의 다른 글
[포트폴리오] react-query 캐싱이 안됨 -> re-render시 초기화 문제 (0) | 2025.02.22 |
---|---|
MVC 패턴과 Flux 패턴 이해하기 (2) | 2025.01.24 |
제어 컴포넌트와 비제어 컴포넌트 (Controlled vs. Uncontrolled) (0) | 2025.01.10 |
리액트 - useReducer (1) | 2024.09.05 |
리액트 - 모달 구현 방법 참조 (0) | 2024.09.05 |