일반적으로 react는 부모 컴포넌트가 렌더링되면 자식 컴포넌트가 렌더링되는 tree 구조를 가지고 있다.
하지만 때때로 이런 tree구조가 불편함을 가져다주기도 해서,
이럴 때 부모-자식 관계를 유지하지만 독립적인 위치에서 렌더링을 하면 훨씬 편리한 경우가 있다.
대표적인 예로 modal은 부모 컴포넌트의 스타일링 속성에 제약을 받아 z-index 등으로 번거로운 후처리를 해줘야한다.
이러한 상황에서 portal을 통해 독립적인 구조와 부모-자식 관계를 동시에 유지할 수 있다면, z-index 등 부모 컴포넌트의 제약에서 벗어날 수 있다.
1. portal로 modal 구현하기
2. dim영역 클릭시 모달 닫기
3, 브라우저 뒤로가기 방지-> 이벤트 처리
1. portal로 modal 구현하기
Portal은 컴포넌트 트리와 별개로 특정 DOM 노드에 React 컴포넌트를 렌더링하는 기능을 제공합니다. 이를 통해 모달과 같은 컴포넌트를 root DOM 트리 외부에서 관리할 수 있습니다.
const Modal: React.FC<ModalProps> = ({ isOpen, title, onClose, children }) => {
if (!isOpen) return null;
useEffect(() => {
const preventGoBack = () => {
history.go(1);
onClose();
};
history.pushState(null, "", location.href);
window.addEventListener("popstate", preventGoBack);
return () => window.removeEventListener("popstate", preventGoBack);
}, [onClose]);
return createPortal(
<ModalOverlay>
<ModalContainer>
<ModalBox>
<ModalHeader>
<h2>{title}</h2>
<CloseButton onClick={onClose}>×</CloseButton>
</ModalHeader>
<ModalContent>{children}</ModalContent>
<ButtonContainer>
<Button onClick={onClose}>취소</Button>
<Button>확인</Button>
</ButtonContainer>
</ModalBox>
</ModalContainer>
</ModalOverlay>,
document.getElementById("modal")!,
);
};
export default Modal;
- createPortal:
- createPortal(children, container)는 두 가지 인수를 받습니다:
- children: 렌더링할 React 노드 (모달 UI 구성).
- container: React 컴포넌트를 렌더링할 DOM 노드. 여기서는 document.getElementById("modal")에 렌더링됩니다.
- 모달이 DOM 트리의 다른 곳(modal ID가 있는 컨테이너)에서 렌더링됩니다.
- createPortal(children, container)는 두 가지 인수를 받습니다:
- document.getElementById("modal"):
- Portal의 렌더링 위치입니다.
- public/index.html에 <div id="modal"></div>를 추가함
Portal을 통해 렌더링:
- 모달은 document.getElementById("modal") 노드에 렌더링됩니다.
- 이를 통해 모달이 부모 컴포넌트의 스타일에 영향을 받지 않고 독립적으로 동작합니다
2. dim 클릭으로 모달 닫기
<ModalOverlay onClick={onClose}>
<ModalContainer onClick={(e) => e.stopPropagation()}>
<ModalBox>
<ModalHeader>
- Dim 클릭 이벤트 (onClick):
- ModalOverlay에 onClick={onClose}를 추가하여 Dim 영역을 클릭하면 모달을 닫습니다.
- 이벤트 전파 차단:
- 모달 내부를 클릭해도 Dim 클릭 이벤트가 발생하지 않도록 ModalContainer에 onClick={(e) => e.stopPropagation()}를 추가.
- e.stopPropagation()은 부모 요소로의 이벤트 전파를 차단합니다.
- 사용법:
- onClose prop에 모달 닫기 함수(setIsModalOpen(false))를 전달하면, Dim 클릭 시 모달이 닫힙니다.
3.브라우저 뒤로가기 방지
const preventGoBack = () => {
history.go(1); // 브라우저를 다시 현재 페이지로 이동
onClose(); // 모달 닫기 등의 동작 수행
};
- history.go(1):
- 브라우저의 히스토리를 수정하여 사용자가 "뒤로가기"를 눌러도 다시 현재 페이지로 이동
- 이렇게 하면 실제로 뒤로가기 동작이 발생하지 않도록 막을 수 있습니다.
- onClose():
- 뒤로가기 이벤트가 발생했을 때 모달을 닫거나 원하는 동작을 수행합니다.
history.pushState(null, "", location.href);
history.pushState:
- 현재 페이지의 히스토리 상태를 추가
- 브라우저의 히스토리 스택에 현재 페이지가 추가되므로 "뒤로가기"를 눌렀을 때 다시 현재 페이지로 돌아오게 됩니다.
window.addEventListener("popstate", preventGoBack);
popstate 이벤트:
- 브라우저 히스토리 상태가 변경되었을 때 발생하는 이벤트
- 뒤로가기가 발생하면 preventGoBack 함수가 호출되어 뒤로가기를 방지하고 원하는 동작(onClose)을 수행합니다.
useEffect(() => {
const preventGoBack = () => {
history.go(1);
onClose();
};
history.pushState(null, "", location.href);
window.addEventListener("popstate", preventGoBack);
return () => window.removeEventListener("popstate", preventGoBack);
}, [onClose]);