이번 편에서는 React Portals를 활용하는 개인적인 방법을 소개하겠습니다.
React Portals를 어떻게 활용할 수 있는지 참고자료로써 쓰이기를 희망합니다.
import React, { useEffect, useMemo } from 'react'
import { createPortal } from 'react-dom'
const Portal = ({ id, children }) => {
const [parentElement, cleanupParentElement] = useMemo(() => createParentElement(id), [])
useEffect(() => () => cleanupParentElement(), [])
return createPortal(children, parentElement)
}
const createParentElement = id => {
const rootElement = getRootElement(id)
const parentElement = document.createElement('div')
rootElement.appendChild(parentElement)
const cleanupParentElement = () => {
rootElement.removeChild(parentElement)
}
return [parentElement, cleanupParentElement]
}
const getRootElement = id => {
const rootElement = document.getElementById(id)
if (rootElement !== null) {
return rootElement
}
const nextRootElement = document.createElement('div')
nextRootElement.setAttribute('id', id)
document.body.appendChild(nextRootElement)
return nextRootElement
}
크게 3단계 구성입니다.
추가적으로 메모리 누수를 막기 위하여 Portal을 종료하면 해당 Parent 엘리먼트를 제거하는 로직이 있습니다.
같은 id이면 그 엘리먼트 안에 새로운 Parent 엘리먼트를 추가하는 원리로써 아래의 장점을 가집니다.
/public/index.html
를 수정할 필요가 없다const App = () => (
<>
<div>App</div>
<Dialog>
<User />
</Dialog>
</>
)
const Dialog = ({ children }) => (
<Portal id='dialog'>{children}</Portal>
)
const User = () => (
<div>User</div>
)
위에서 작성한 Portal 컴포넌트를 응용하면 다양한 기능을 유연하게 추가할 수 있습니다.
const EscapeablePortal = ({ id, isOpen, close, children }) => {
const closePortalToClickEscapeKey = () => {
const closePortal = event => {
if (event.key === 'Escape') {
close()
}
}
document.addEventListener('keydown', closePortal)
return () => {
document.addEventListener('keydown', closePortal)
}
}
useEffect(closePortalToClickEscapeKey, [close])
if (!isOpen) {
return null
}
return (
<Portal id={id}>{children}</Portal>
)
}
키보드의 ESC 키를 클릭하면 Portal을 종료하는 컴포넌트입니다.
좋아요와 댓글 감사합니다.
오탈자, 질문 등은 언제든지 댓글로 달아주세요!