React & TypeScript & styled-components로 Modal 공통컴포넌트 만들기
우선 모달창의 옵션에는
1. x버튼을 누르면 창이 닫혀야한다.
2. EscKey를 누르면 창이 닫혀야한다.
3. 모달창 외의 외부를 누르면 창이 닫혀야한다.
3가지 조건을 충족하도록 만들었다.
우선 모달창 상태와 이를 관리할 수 있는 훅을 제작한다.
useModal.ts
import { useState } from "react";
export const useModal = () => {
const [isOpen, setIsOpen] = useState(false);
const openModal = () => {
setIsOpen(true);
};
const closeModal = () => {
setIsOpen(false);
};
return {
isOpen,
openModal,
closeModal,
};
};
Modal.tsx
const Modal = ({
isOpen = true,
canBackdropClose = true,
canEscKeyClose = true,
hasCloseButton = true,
closeModal,
children,
...rest
}: Props)
우선 맨위에 정의했던 3개는 모두 프로퍼티에 true값을 디폴트로 넣어준다.
그리고 Modal창이 열렸는지 여부를 확인하기 위해 isOpen값 또한 넣어주고 모달창을 닫는 closeModal과 내부 컨텐츠(자식 요소)로 받을 children, 다른 선언되지 않았지만 받을 수도 있게 되는 프로퍼티 rest를 받아준다.
const onKeyDownEscape = useCallback(
(event: KeyboardEvent) => {
if (event.key !== "Escape") return;
closeModal();
},
[closeModal],
);
onKeyDownEscape는 키보드 이벤트가 발생했을 시에만 호출되는 함수이다.
useCallback으로 closeModal이 발생하지 않을시 이 함수는 리랜더링시 재생성 되지 않도록 하여 성능 최적화를 하려고 했다.
키가 Escape가 아니면 함수를 종료하고 맞다면 모달창을 닫도록 해주었다.
useEffect(() => {
if (isOpen && canEscKeyClose) {
window.addEventListener("keydown", onKeyDownEscape);
}
return () => {
window.removeEventListener("keydown", onKeyDownEscape);
};
}, [isOpen, canEscKeyClose, onKeyDownEscape]);
모달창이 열렸고 esc로 모달창을 닫을 수 있게 했다면 키보드 이벤트 리스너를 추가하여 onKeyDownEscape을 실행한다.
모달이 닫힌다면 이벤트 리스너를 제거한다.
(제거하지 않는다면 이벤트 리스너가 계속 유지되기 때문에 불필요하게 메모리가 증가하거나 예상치못한 동작을 불러일으킬 수 있음)
그리고 모달창이므로 해당 컴포넌트와는 독립적으로 존재할 필요가 있다.
import { createPortal } from "react-dom";
createPortal을 사용하여 현재 DOM트리에서 벗어나 독립적으로 존재할 수 있게 한다.
부모DOM위에 존재해야하므로 document.body위에 생성되도록 한다.
return createPortal(
<>
{isOpen && (
<>
<Backdrop onClick={canBackdropClose ? closeModal : undefined} />
<Content {...rest}>
{hasCloseButton && (
<CloseButton
type="button"
onClick={closeModal}
aria-label="모달 닫기"
>
<IoMdClose width={24} height={24} />
</CloseButton>
)}
{children}
</Content>
</>
)}
</>,
document.body,
);
커스텀하여 만들 수 있는 Modal 공통 컴포넌트 전체 코드는 아래와 같다.
위의 Modal 컴포넌트를 이용하여 로그인 때 사용할 Modal창을 만들었다.
type LoginModalProps = {
isOpen: boolean;
closeModal: () => void;
};
const LoginModal = ({ isOpen, closeModal }: LoginModalProps) => {
return (
<Modal isOpen={isOpen} closeModal={closeModal}>
<Container>
<Title>로그인</Title>
<Content>로그인테스트</Content>
</Container>
</Modal>
);
};
Container에 모달안에 들어갈 내용을 넣어준다.
그리하여
<StyledRightSection>
<TextButton
text="로그인"
colorType="dark"
type="button"
onClick={openModal}
/>
<LoginModal isOpen={isOpen} closeModal={closeModal} />
</StyledRightSection>
이렇게 로그인버튼을 누르면 openModal로 isOpen이 true로 바뀌고 LoginModal이 나타나게 된다.]
위와 같이 현재 프로젝트에 적용하여 사용하고 있다.
'프로젝트 > Resthub' 카테고리의 다른 글
[Resthub#개발일지6] React에서 공공데이터 API 가져오기 (+전처리까지) (1) | 2024.01.21 |
---|---|
[Resthub#개발일지5] Key를 모르는데 어떻게 가져와요? (0) | 2024.01.21 |
[Resthub#개발일지4] 전역 상태 관리 Context API를 Zustand로 변경하다 (0) | 2024.01.08 |
[Resthub#개발일지3] Firebase의 구글 Login & Logout 기능 구현 (0) | 2024.01.04 |
[Resthub#개발일지2] Firebase와 React 연동하기 (1) | 2023.12.31 |