본문 바로가기
개발 공부 일지/React

전역 모달이 필요한 이유

by yelimu 2025. 1. 15.

 

세번째 프로젝트 Linkbrary 에서 내가 구현한 부분은 zustand를 이용한 전역 모달 

 

세번째 프로젝트를 마치고

지난 포스팅 두번째 프로젝트 회고 말미에 언급한 세미 프로젝트 회고이다.이제 진짜 내일부터 최종 프로젝트 기간이라 후딱 회고 작성하려고 한다.  코드잇 부트캠프에서 진행하는 정식 프로

memoryelim.tistory.com

 

 

 

💣모달을 왜 전역으로 구현했냐는 질문에 

 

💥 어.? 당연히 전역으로 하는거 아니었나?! 

음.. 모달이 여러개라서? 

여러 모달이 여러 페이지에서 띄워져서? 

 

첫번째 프로젝트를 제외하고 나머지 프로젝트에서 모달을 전역으로 구현했기에 당연스럽게 여기고 있었다. 

개발하는데 있어서 배운건 '당연히' 라는게 없다는 것이다.

뭐든 이유가 있다. 그런데도 나는 또 아무 생각 없이 코드를 작성하고 있었구나 !!

 

전역 모달이 필요한 이유에 대해 알아보려고 한다.


모달이란? 

모달은 사용자가 웹사이트나 앱을 사용할 때 나타나는 대화 상자나 팝업 창으로,

사용자의 주의를 특정 작업이나 정보에 집중시키는 데 사용된다

 
💣 여러 페이지 / 컴포넌트에서, 여러 종류의 모달을 띄워줘야 한다면? 
 

💥  모달이 필요한 매 페이지마다 반복되는 코드 발생

- 모달 컴포넌트 import
- 로컬 상태 정의 (useState)
- { isOpenModal && <Modal/> }으로 컴포넌트 렌더 
 
 
🎲 모달 구현 시 필요한 상태 
1. 모달 열림 여부
2. 호출하는 모달의 종류 (UI)
3. 모달 호출 시 전달하는 prop (모달에서 보여줄 data) 
 
이 아이들을 전역상태를 관리하는 store에서 정의해주자 

 

🙆‍♀️ 전역 모달을 사용함으로써 얻는 이점 

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 });
  };

 


모달 관련해서 리팩토링한 분량이 많지는 않지만 그래도 조금이나마 더 좋은 코드가 되었다는 생각에 뿌듯하다 ^- ^

뭣모르고 쓴 코드였지만 포스팅을 하면서 조금씩 내것이 되어가는 것 같다