portal이란?
- 리액트(React)에서 portal은 컴포넌트 트리 내에서 다른 위치로 자식 요소를 렌더링할 수 있는 기능이다. portal은 주로 모달 창, 팝오버, 툴팁과 같은 오버레이 컴포넌트를 구현하거나, 외부 DOM 요소에 컴포넌트를 렌더링할 때 사용된다.
- portal을 사용하면 자식 요소는 DOM 트리의 지정된 위치로 이동되지만, 해당 자식 컴포넌트는 여전히 컴포넌트 트리의 일부로 유지된다.
- portal을 사용하면 컴포넌트가 렌더링되는 위치와 실제로 화면에 표시되는 위치를 분리할 수 있으며, 레이아웃, 스타일링, 이벤트 처리 및 zIndex 관리를 보다 쉽게 할 수 있게 해준다.
- portal은 보통 모달창을 구현할 때, 많이 사용된다.
- 프로젝트에서도 모달창을 구현할 때, portal을 사용했고 처음 portal을 사용한 코드는 아래와 같다.
// index.html
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="modal-root"></div>
<div id="root"></div>
</body>
// portal을 사용한 Menu 컴포넌트
export const Menu = (...) => {
return createPortal(
<>
<BackDrop onClick={backDropHandler} />
<Menu>
...
</Menu>
</>,
document.querySelector('#dropdown-root') ?? document.body
);
};
- index.html에 id가 "modal-root"인 div를 하나 만들고, portal을 사용해 Menu 컴포넌트를 "modal-root"로 이동시켰다.
의문점
- 프로젝트에서 처음 portal을 사용할 때는 이전 리액트 학습자료와 구글링을 통해 portal을 사용한 다른 글들을 참고했다.
- 하지만, 한 가지 의문점이 있었다. 모달창이 렌더링되지 않는 상황에서 사용하지 않는 div("modal-root")가 남아있는 것 대신에 portal을 사용할 때만 div를 만들고 사용하지 않을 때는 없앨 수 있지 않을까?
- 이러한 의문을 프론트엔드 리뷰어에게 질문을 했고, 리뷰어로부터 참고 글을 받았다.
- 나는 리뷰어가 남겨준 참고자료(https://medium.com/@jc_perez_ch/dynamic-react-portals-with-hooks-ddeb127fa516)를 참고하여 동적 portal 컴포넌트를 만들기로 했다.
시도한 방법 및 해결방안
- 동적 portal의 내용은 다음과 같다.
- 첫 번째, portal 컴포넌트에 전달된 id prop과 일치하는 div가 있는지 찾고, 일치하는 div가 없으면 div를 만들고 modalDiv에 저장한다.
- 두 번째, modalDiv의 부모 요소가 있는지를 확인하여 만약 부모 요소가 없다면 useEffect 내부에서 id prop으로 modalDiv의 id를 설정하고, document.body에 추가한다.
- 세 번째, useEffect의 클린업 함수로 컴포넌트가 언마운트되면 동적으로 생성한 div를 삭제한다.
// Portal 컴포넌트
import React, { useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
const Portal = ({ id, children }) => {
const modalDiv = useRef(
document.getElementById(id) || document.createElement('div')
);
const dynamicDiv = !modalDiv.current.parentElement;
useEffect(() => {
if (dynamicDiv) {
modalDiv.current.id = id;
document.body.appendChild(modalDiv.current);
modalDiv.current.classList.add('modal-root');
}
return () => {
modalDiv.current.remove();
};
}, [id]);
return createPortal(
<S.PortalWrap>
<S.Portal>{children}</S.Portal>
</S.PortalWrap>,
modalDiv.current
);
// 동적 portal을 사용한 Menu 컴포넌트
export const Menu = (...) => {
return <Portal id="modal-root">
<BackDrop/>
<Menu>
...
</Menu>
</Portal>
};
- 위와 같이 동적으로 portal을 생성하여 사용하면서, index.html에는 portal을 사용하지 않을 때에 불필요한 정적 div가 남아있지 않다.
배운 점
- Portal을 사용하여 동적으로 부모 div 요소를 생성하는 과정에서 React 컴포넌트의 생명주기에 대해 이해할 수 있었고, 컴포넌트의 마운트 및 언마운트 시점에 로직을 실행하여 동적으로 DOM 요소를 조작하는 방법을 학습할 수 있었다.
- 개발은 역시 내가 생각하는 부분을 실제로 만들어내는 과정에서 충분히 매력적인 것 같다.
- 또한, 내가 고민했던 부분에 대해서 충분히 필요한 고민이라며 참고자료를 건네준 리뷰어에게 감사했고, 다음에 누군가 나에게 질문을 한다면 참고자료와 함께 도움을 줄 수 있는 사람이 되어야 겠다. ✌️
참고 : https://pocoding.tistory.com/117
https://medium.com/@jc_perez_ch/dynamic-react-portals-with-hooks-ddeb127fa516
'프로젝트 > 세컨핸드' 카테고리의 다른 글
[Trouble Shooting] UUID를 사용한 고유한 키 값 사용 (0) | 2024.01.23 |
---|---|
[Trouble Shooting] 사용자 디바이스에 따른 다수의 이미지 조회 방법 (0) | 2024.01.22 |
[기능 구현] 커스텀 훅(Custom Hook)을 사용하여 비동기 로딩 및 에러 상태 처리 (0) | 2024.01.22 |
[기능 구현] OAuth를 통한 로그인 기능 구현 (0) | 2024.01.22 |
[Trouble Shooting] 상품 수정 등록 후 뒤로가기 경로 문제 (0) | 2023.11.23 |