자주 쓰이는 컴포넌트들을 직접 구현하고, 그 원리를 분석하는 시간을 가지려고 한다.
그 첫번째로 모달창을 구현해보았다.
전체 코드는 글의 맨 아래부분에 작성해두었다.
- 기본적인 모달 구현
- 화면 외곽을 누르면 닫히도록 처리
type ModalProps = {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
}
모달에 전달될 프롭스들의 타입을 선언해준다.
export const Modal = ({ isOpen, onClose, children}: ModalProps) => {
// 코드 내용 작성
}
메인 함수에 프롭스를 타입과 함께 전달한다.
useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleEsc);
return () => document.removeEventListener('keydown', handleEsc);
}, [onClose]);
UseEffect 를 통해서 키보드에서 esc 키를 누르면 모달창이 닫히도록 로직을 작성했다.
- 컴포넌트 마운트시 : Escape 이벤트 처리
- 컴포넌트 언마운트시 : 키다운 이벤트 리스너를 제거
여기서 e:KeyboardEvent 는 이벤트 객체의 타입이 KeyboardEvent 임을 나타낸다.
if (e.key === 'Escape') { onClose(); }
유저가 누른 키가 'Escape' 키인지 확인하고 맞으면 onClose 호출
1. document.addEventListener('keydown', handleEsc);
2. return () => document.removeEventListener('keydown', handleEsc);
컴포넌트가 마운트 될 때 1번 코드 실행 -> 이벤트 리스너가 유저의 키보드 이벤트 감지 시작
컴포넌트가 언마운트 될 때 2번 코드 실행 (useEffect 의 cleanUp 함수) -> 이벤트 제거
if (!isOpen) return null;
isOpen 의 값이 false 면, 아무것도 렌더링하지 않겠다.
리액트에서 아무것도 렌더링하지 않고 싶으면 null을 리턴해주면 된다.
const handleModalClick = (e: React.MouseEvent) => {
e.stopPropagation();
};
e: React.MouseEvent 이벤트 객체의 타입이 리액트의 MouseEvent 임을 나타낸다.
해당 객체는 클릭 이벤트와 관련된 정보를 담고 있다.
e.stopPropagation() 은 이벤트 객체의 메서드이다.
이 메서드는 이벤트의 전파(propagation)를 막는 역할을 한다.
여기서 말하는 이벤트의 전파란 DOM 트리에서 이벤트가 발생했을 때,
해당 이벤트가 부모 요소나 자식 요소로 전달되는 현상을 의미한다.
stopPropagation() 을 호출하면 이벤트가 더 이상 상위 요소로 전파되지 않는다.
- 모달 창과 같은 UI 요소에서 stopPropagation() 은 이벤트 버블링을 방지하는 데 사용된다.
- 모달 창 내부의 특정 요소를 클릭했을 때, 해당 클릭 이벤트가 모달 창의 배경이나 다른 상위 요소로 전달되는 것을 막는다.
결론 : 모달창 내부를 눌렀을땐 모달창이 닫히지 않게 막는다.
return ReactDOM.createPortal(JSX, DOM_Container);
React DOM API 는 React 컴포넌트의 렌더링 결과를 DOM 트리의 다른 위치에 렌더링 할 수 있게 해준다.
총 두 개의 인자를 받는데, 첫 번째 인자는 렌더링할 JSX 이고 두 번째 인자는 렌더링될 DOM 컨테이너 요소를 받는다.
createPortal()을 사용하면 컴포넌트가 DOM 트리의 특정 위치에 독립적으로 렌더링되므로
부모 컴포넌트의 스타일이나 레이아웃에 영향을 받지 않고 원하는 위치에 그려낼 수 있다.
주로 모달, 툴팁, 드롭다운 메뉴와 같이 최상위 레이어에 표시되어야 하는 UI 들을 렌더링할때 사용된다.
document.body 는 HTML 문서의 'body' 요소를 나타내는 DOM 요소이다.
createPortal()의 두 번째 인자로 전달하면, 모달 창이 'body' 요소의 자식으로 렌더링된다.
-> 모달창을 웹의 최상위 레이어에 표시하기 아주 좋은 방법!
추가로 ReactDOM은 import from 'react-dom' 으로 명시하는 것이 좋다.
결론 : createPortal()을 사용하면 특정 DOM 엘리먼트에 내가 작성한 JSX 코드를 꽂아서 렌더링 할 수 있다.
코드 전문
import { ReactNode, useEffect } from 'react';
import ReactDOM from 'react-dom';
import style from './newModal.module.css';
type ModalProps = {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
};
export const NewModal = ({ isOpen, onClose, children }: ModalProps) => {
useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleEsc);
return () => document.removeEventListener('keydown', handleEsc);
}, [onClose]);
if (!isOpen) return null;
const handleOverlayClick = () => {
onClose();
};
const handleModalClick = (e: React.MouseEvent) => {
e.stopPropagation();
};
return ReactDOM.createPortal(
<div className={style.overlayStyle} onClick={handleOverlayClick}>
<div className={style.modalStyle} onClick={handleModalClick}>
<button onClick={onClose} className={style.closeButtonStyle}>
닫기
</button>
{children}
</div>
</div>,
document.body
);
};
CSS
.overlayStyle {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modalStyle {
background-color: #fff;
padding: 2rem;
border-radius: 8px;
min-width: 300px;
}
.closeButtonStyle {
position: absolute;
margin-left: 16rem;
margin-top: 4rem;
}
앱 진입점
import { useState } from 'react';
import { Modal } from '@src/component/newModal/NewModal.tsx';
export const TestModal = () => {
const [isModalOpen, setModalOpen] = useState(false);
return (
<>
<h1> 모달 예제 </h1>
<button onClick={() => setModalOpen(true)}>모달 열기</button>
<Modal isOpen={isModalOpen} onClose={() => setModalOpen(false)}>
<h2>안녕하세요!</h2>
<p>이건 모달 창입니다.</p>
</NewModal>
</>
);
};
isOpen 프롭스에 isModalOpen 상태를 전달하여 모달 창의 표시 여부를 나타낸다.
onClose 프롭스에 setModalOpen(false) 함수를 전달해서 모달 창을 닫는 동작을 처리한다.
'프론트엔드 > React' 카테고리의 다른 글
2025 최신화 vite, react, typescript 프로젝트 (0) | 2025.03.23 |
---|