HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
정리/
🎟️
모달 컴포넌트 제대로 사용하기
🎟️

모달 컴포넌트 제대로 사용하기

태그
React
refactoring
CheQuiz
ref
https://nakta.dev/how-to-manage-modals-1
⭐
파편화된 모달 관리를 효율적으로 한 곳에서 함으로써 해결 (중앙화된 모달관리)

1. 기존 프로젝트에서의 모달의 문제

  • 모달 사용 부분
    • // Header.tsx const [modalShow, setModalShow] = useState(false); const [modalContent, setModalContent] = useState(''); ... <Button onClick={() => { setModalShow(true) setModalContent('login')}> 로그인 </Button> ... ... {modalShow && ( <Modal content={modalContent} setModalShow={setModalShow} /> )}
  • 모달 컴포넌트
    • // Modal.tsx const Modal = ({ setModalShow, content }: Props) => { const getComponentByContent = (contentType: string) => { switch (contentType) { case 'login': return <LoginForm setModalShow={setModalShow} />; case 'signup': return <SignUpForm setModalShow={setModalShow} />; ... } }; return ( <S.Wrapper onClick={() => {setModalShow(false)}}> {getComponentByContent(content)} </S.Wrapper> ); }; export default Modal;
[문제점]
  • 모달 사용 부분에 모달 상태를 관리하기 위한 부수적인 코드가 많아진다. (modalShow, modalContent 에 대한 useState, 매번 modalShow에 따른 <Modal>을 하단에 입력해주어야함)
    • props로 모달을 열고 닫기 위한 setter 함수를 반드시 넘겨주어야 한다.
      • 즉 모달컴포넌트 자체가 사용하는 부모컴포넌트에 결합될 수 밖에 없는 구조
  • 모달의 비즈니스 로직을 사용하는 부분에서 알 수 없다.
    • 모달의 핵심은 어떤 모달을 열것인지, 취소-확인에 따른 비즈니스 로직은 무엇인지를 사용 부분에서 알 수 있어야 한다.
    • 현재 구조에서는 사용 부분이 아닌 Modal.tsx의 switch문 내의 비즈니스 로직이 작성되는 구조로 모달의 역할과 책임이 분산되어 있다.
  • 확장성이 부족한 모달 컴포넌트 설계
    • 여러개의 모달이 필요한 경우 대응하기 어렵다.
    • 현재 모달의 경우, 취소 및 확인 에 대한 비즈니스 로직에 대응하기 어렵다.
    •  
[해결방법]
⭐
모달을 열고 닫는 상태와 비즈니스 로직 처리를 위한 코드를 분리해낸다.

1.모달을 열고 닫는 상태는 ContextAPI를 통해 전역으로 관리한다.

  • 효과 modal을 열고 닫기 위한 불필요한 props 제거
  • 효과 여러개의 모달을 사용하더라도 중복된 코드가 발생하지 않는다 (상태관리 직접하지 않기)
 
  • How 1) 현재 열려져 있는 모달 상태와 이를 dispatch 하는 함수를 Context API를 통한 전역상태로 관리
    • // ModalsContext.js import { createContext } from 'react'; export const ModalsDispatchContext = createContext({ open: () => {}, close: () => {}, }); export const ModalsStateContext = createContext([]) // {Component, props}[]
    • 참고 : 두 개의 Context로 분리하는 것은 useModal Hook에서 dispatch Context만을 사용하여, hook을 사용하는 컴포넌트에서 리렌더링이 일어나는 이슈를 방지하기 위함
    • ModalState 는 렌더링할 모달 Component와 해당 모달에서 사용할 props 형태로 저장하여 준다.
    •  
  • How 2) 모달을 열고 닫기 위한 useModal hook 구현
    • import { useContext } from "react"; import { ModalDispatchContext } from "./ModalContext"; export default function useModals() { const { open, close } = useContext(ModalDispatchContext); const openModal = (Component, props) => { open(Component, props); }; const closeModal = (Component) => { close(Component); }; return { openModal, closeModal, }; }
       
      • before
        • const [modalShow, setModalShow] = useState(false); const [modalContent, setModalContent] = useState(''); return ( <Button onClick={() => { setModalShow(true); setModalContent('login') }}> 로그인 </Button> {modalShow && ( <Modal content={modalContent} setModalShow={setModalShow} /> )} )
      • after
      const { openModal } = useModals(); return ( <button onClick={() => { openModal(LoginModal, { onSubmit: ... , }); }}> 로그인 </button> )
      • 모달 상태 관리를 위한 코드가 useModals hook으로 처리 되면서, Modal의 핵심(열리는 컴포넌트, 비즈니스 처리를 위한 핸들러)에 대한 가독성이 명확해짐
       

      2. 모든 모달에 공통적으로 적용할 부분과 각 컴포넌트의 비즈니스 로직 처리를 구분한다.

    • 효과 공통 모달 로직과 비즈니스 로직을 분리할 수 있어 유지보수에 유리하다.
    • How 1) 공통 모달 로직은 Modal을 렌더링하는 부분에서 정의하여, 각 모달 컴포넌트의 props로 내려준다.
      • // Modals.js import { useContext } from "react"; import { ModalDispatchContext, ModalsStateContext } from "./ModalContext"; const Modals = () => { const opendModals = useContext(ModalsStateContext); const { close } = useContext(ModalDispatchContext); return opendModals.map((modal, index) => { const { Component, props } = modal; const { onSubmit, ...restProps } = props; const onClose = () => { close(Component); }; const handleSubmit = async () => { onSubmit && (await onSubmit()); onClose(); }; return ( <Component {...restProps} key={index} onClose={onClose} onSubmit={handleSubmit}></Component> ); }); }; export default Modals;
         
    • 추가
      • onSubmit() 함수가 매개변수를 필요로 한다면, Modals에서 공통으로 처리하는 것이 아닌, 각 모달 컴포넌트 단에서 인자를 넘겨줄 수 있도록 한다.
        • // App <button onClick={() => { openModal(modals.login, { onSubmit: (value) => console.log(value), // '매개변수-value' }); }}> 로그인 모달 열기 </button> //LoginModal const handleClickSubmit = () => { onSubmit('매개변수-value'); onClose(); }; // Modals <Component {...props} onSubmit={onSubmit}></Component>

      3. 모달 컴포넌트 중앙에서 관리하기

    • 효과 Lazy로딩을 통해 모달이 실제로 사용되는 순간에 import 되어 초기 로딩 속도 향상을 가져온다.
    • how 사용되는 모든 모달 컴포넌트를 객체(이름:component)로 매핑하고, 사용부분에서는 컴포넌트가 아닌 이름을 통해 불러온다.
      • // Modals.js export const modals = { basic: lazy(() => import("./BasicModal")), login: lazy(() => import("./LoginModal")), }; const Modals = () => { ... } // App.js import { modals } from './Modals' <button onClick={() => { openModal(modals.login, { onSubmit: () => console.log("로그인 제출"), }); }}> 로그인 모달 열기 </button>
      • 하나의 모달이 불러와 질 때, 모든 모달이 한 번에 로딩되는 것을 방지하기 위해 dynamin import 기법을 적용한다. (React.lazy 이용)
        • export const modals = { basic: lazy(() => import("./BasicModal")), login: lazy(() => import("./LoginModal")), }; const Modals = () => { ... return ( <Suspense> <Component {...restProps} key={index} onClose={onClose} onSubmit={handleSubmit}></Component> </Suspense> ) }
          • 최초 로딩 시, Modal 컴포넌트는 로드 되지 않는다.
            • notion image
           
          • 로그인 모달을 열었을 때, LoginModal 모듈이 로드된다.
          notion image