-
[React] 전역 모달 구현하기 :: 마이구미React 2021. 11. 3. 22:00반응형
이 글은 React 에서 전역 모달을 구현하는 방법을 다룬다.
코드 예제는 React + Recoil + Typescript 로 구성되어있다.
Recoil 은 단순 모달 on/off 상태 관리 용도로 학습을 거의 요구하지 않는다. (코드는 3줄이다)
참고 글 - https://opensource.com/article/21/5/global-modals-react
코드 예제 - https://codesandbox.io/s/bitter-brook-rep1f?file=/src/recoil/modal.ts:530-621이 글에서 "전역 모달" 을 의미하는 것은 전역에서 관리되는 상태를 가지는 모달이다.
반대로 지역으로 관리되는 상태를 가지는 모달을 확인해보자.
모달을 import 하고 모달의 on/off 를 관리하는 state 와 필요 시 props 을 요구하게 된다.
const Parent = () => { const [isModal, setIsModal] = useState(); // ... return ( <> <Modal isModal={isModal} /> <Child items={items} setIsModal={setIsModal} /> </> ) }
이게 뭐가 문제가 되는 것인가?
만약 모달이 window.alert() 이나 window.confirm() 과 같은 형태를 커스텀한 모달이라면 어떨까?
정말 많은 곳에서 사용하게 될 것이다.
흔히 다음과 같은 사례가 있다.
- POST, PUT, DELETE 와 같은 API 요청한 후 성공이나 실패에 따른 얼럿창
- 예기치 못한 에러 발생 시 얼럿창
- Form 작성 도중 페이지 이동 시 컨펌창
- 저장 버튼 클릭 시 컨펌창
window.alert(), window.confirm() 으로 한정적으로 보더라도 굉장히 많은 경우가 존재한다.
다른 곳에서도 모달이 필요하면 각 컴포넌트에서는 위처럼 매번 작성해야한다.
하지만 전역으로 관리되는 모달을 있다면, 매번 각 컴포넌트에서 위와 같은 코드를 작성할 필요가 없어진다.
코드 작성에 앞서, 전역 모달이 완성되면 달라지는 그림을 한번 미리 확인해보자.
모달 import 측면에서는 다음과 같다.
<Parent1> <ConfirmModal /> </Parent1> <Parent2> <AlertModal /> </Parent2> <Parent3> <ConfirmModal /> </Parent3>
전역 모달이 아니라면 각 컴포넌트에서는 필요한 모달을 각각 import 해야했다.
전역 모달이라면 다음과 같은 코드로 작성된다.
<> <GlobalModal /> </> <Parent1> ... </Parent1> <Parent2> ... </Parent2>
최상단 레벨에 미리 GlobalModal 을 import 하기 때문에 다른 곳에서는 할 필요가 없게 된다.
매번 선언하는 것이 아닌 최상단에 한번 선언해놓으면 된다.
추가로 GlobalModal 에서는 다양한 모달(ConfirmModal, AlertModal) 을 담고 있게 된다.
모달 on/off 제어 측면은 다음과 같다.
const Parent = () => { const [isModal, setIsModal] = useState(); return ( <> <Modal isModal={isModal} /> <Child items={items} setIsModal={setIsModal} /> </> ) }
위에서 언급한대로, 모달을 import 하고 모달의 on/off 를 관리하는 state 와 필요 시 props 을 요구하게 된다.
전역 모달이라면 다음과 같은 코드로 작성된다.
const Child = () => { const { showModal } = useModal(); const handleClick = () => { showModal({ modalType: 'AlertModal' }); // showModal({ modalType: 'ConfirmModal' }); } return ( <> <Button onClick={handleClick} /> </> ) }
모달 import, state 없이 단순 커스텀 훅으로 된 showModal 함수를 통해 모달을 제어하게 된다.
크게 2가지 측면에서 달라지는 모습을 간략하게 살펴보았다.
이제는 전역 모달에 필요한 각각의 코드들을 하나씩 확인해보자.
(하단 결과물 존재 - CodeSandbox)
파일 기준으로는 다음과 같이 작성될 것이다.
- GlolbalModal.tsx - 전역 모달 컴포넌트
- AlertModal.tsx - 커스텀 얼럿창 컴포넌트
- ConfirmModal.tsx - 커스텀 컨펌창 컴포넌트
- useModal.ts - 커스텀 훅
- modal.ts - 전역 상태 관리
GlobalModal.tsx
import React from "react"; import { useRecoilState } from "recoil"; import ConfirmModal from "./ConfirmModal"; import AlertModal from "./AlertModal"; import { modalState } from "../recoil/modal"; export const MODAL_TYPES = { ConfirmModal: "ConfirmModal", AlertModal: "AlertModal" } as const; const MODAL_COMPONENTS: any = { [MODAL_TYPES.ConfirmModal]: ConfirmModal, [MODAL_TYPES.AlertModal]: AlertModal }; const GlobalModal = () => { const { modalType, modalProps } = useRecoilState(modalState)[0] || {}; const renderComponent = () => { if (!modalType) { return null; } const ModalComponent = MODAL_COMPONENTS[modalType]; return <ModalComponent {...modalProps} />; }; return <>{renderComponent()}</>; }; export default GlobalModal;
- MODAL_TYPES 는 다양한 모달을 의미하는 Key 용도이다.
- MODAL_COMPONENTS 는 실제 다양한 모달 컴포넌트가 객체의 Key 에 따라 import 되어있다.
- useRecoilState 는 단순히 store 에 있는 모달의 state 라고 보면 된다.
- 모달의 state 는 modalType, modalProps 으로 2가지로 구성되어있는 모습을 볼 수 있다.
- modalType 은 MODAL_TYPES 의 타입으로 정의되어 "ConfrimModal" 또는 "AlertModal" 을 의미하고, modalProps 는 optional 로 모달 컴포넌트에 props 로 전달할 요소들이다.
renderComponent 함수에서 모달의 state 인 modalType 을 통해 특정 모달 컴포넌트를 선택하고 modalProps 를 컴포넌트에 넘겨주는 모습을 볼 수 있다.
즉, store 에 저장된 modalType 이 "AlertModal" 이라면 MODAL_COMPONENTS["AlertModal"] 을 의미하여 AlertModal 컴포넌트가 된다.
modal.tsx
import { atom } from "recoil"; import { MODAL_TYPES } from "../components/GlobalModal"; import { ConfirmModalProps } from "../components/ConfirmModal"; import { AlertModalProps } from "../components/AlertModal"; const { ConfirmModal, AlertModal } = MODAL_TYPES; export interface ConfirmModalType { modalType: typeof ConfirmModal; modalProps: ConfirmModalProps; } export interface AlertModalType { modalType: typeof AlertModal; modalProps: AlertModalProps; } export type ModalType = ConfirmModalType | AlertModalType; export const modalState = atom<ModalType | null>({ key: "modalState", default: null });
모달 state 를 관리하는 store 를 의미한다.
modalState 는 modalType, modalProps 2가지 값을 가지게 된다.
AlertModal 과 Confirm 모달이 가지는 modalProps 는 서로 다르다.
예를 들어 alertModal 은 확인용 버튼이 하나만 존재하면 되고, confirmModal 의 경우에는 확인, 취소 2가지 버튼을 제공해줘야한다.
그래서 ConfirmModalType, AlertModalType 각각 정의하여, 타입 추론을 더 명확하게 해줄 수 있다.
useModal.ts
import { useRecoilState } from "recoil"; import { ModalType, modalState } from "../recoil/modal"; export default function useModal() { const [modal, setModal] = useRecoilState(modalState); const showModal = ({ modalType, modalProps }: ModalType) => { setModal({ modalType, modalProps }); }; const hideModal = () => { setModal(null); }; return { modal, setModal, showModal, hideModal }; }
유용하게 재사용에 도움을 주는 방법으로 커스텀 훅으로 store 에 있는 모달 state 를 가져와서 맵핑 하는 모습을 볼 수 있다.
커스텀 훅의 반환값을 보다시피 실제 컴포넌트에서는 showModal, hideModal 을 사용하게 된다.
App.tsx
import useModal from "./hooks/useModal"; import GlobalModal from "./components/GlobalModal"; export default function App() { const { showModal } = useModal(); const handleClickAlertModal = () => { showModal({ modalType: "AlertModal", modalProps: { message: "Success!" } }); }; const handleClickConfirmModal = () => { showModal({ modalType: "ConfirmModal", modalProps: { message: "Yes or No", confirmText: "Yes", cancelText: "No", handleConfirm: () => { console.log("Yes!"); }, handleClose: () => { console.log("No!"); } } }); }; return ( <div className="App"> ... </div> ); }
전역으로 모달을 관리하는 것이 상황에 따라 복잡함 없이 더 좋은 코드로 작성할 수 있게 된다.
전체 예제 코드를 확인해보면 더 도움이 될 것이다.
반응형'React' 카테고리의 다른 글
[React] react-router v6 에서 Prompt 구현하기 :: 마이구미 (3) 2022.04.17 [React] 중첩 라우터 언제 사용하는가? :: 마이구미 (0) 2022.03.13 Redux 를 걷어낸 이유 :: 마이구미 (2) 2021.07.06 SPA 에서 SEO 적용하기 :: 마이구미 (5) 2021.01.24 Warning Received `true` for non-boolean attribute :: 마이구미 (8) 2020.11.06