한달 전쯤에 Udemy React 강의를 통해 portal로 modal를 구현하는 새로운 방법에 대해 알게 되었었다.
이 후에, 타스 플젝에 모달을 적용해야 해서 이왕 구현하는거 portal를 이용해서 모달을 구현해보기로 했다.
- 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 방법
createPortal
을 사용하여 해당 컴포넌트의 HTML 내용을 다른 곳으로 포털, 즉 이동시킬 수 있다!
Portal을 이용하면 DOM 트리 상에서 부모 컴포넌트의 영향을 받지 않도록 최상위 계층으로 컴포넌트를 옮김으로써 원치 않는 CSS 등의 방해를 받지 않을 수 있다는 것이 장점!! 👍
ReactDOM.createPortal(child, container)
// 첫 번째 인자(child): 렌더링할 수 있는 React 자식
// 두 번째 인자(container): DOM 엘리먼트
먼저, Portal를 사용하려면 public > index.html 파일 내 body 태그 내에
modal에 해당하는 root
와modal 뒷 배경에 해당하는 root
를 설정해준다.
<!-- index.html -->
<body>
<div id="backdrop-root"></div>
<div id="overlay-root"></div>
<div id="root"></div>
</body>
이후에, 모달 뒷배경에 해당하는 컴포넌트를 설정해준다. 타스로 구현하는 것이므로 type도 설정해주기!
// 모달 뒷배경
interface Prop {
onClose?: () => void
}
const Backdrop: React.FC<Prop> = ({ onClose }) => {
return <S.Backdrop onClick={onClose} />
}
윗 부분 구현하던 도중에 아래와 같은 에러가 났었는데 이 에러는 div 태그에 onClick을 사용해서 나는 에러였다.
👀 해결 방법: div 태그를 button 태그로 바꿔주니까 에러 해결~!
다음으로, 모달 부분에 해당하는 컴포넌트를 구현해준다. + type까지 지정해주기!
// 모달 부분
interface Props {
title?: string
value?: string
children?: React.ReactNode
onConfirm?: () => void
onClose?: () => void
onChange?: (e: React.FormEvent<HTMLInputElement>) => void
}
// 여기서 props로 넘겨주고, 타입 설정하면 에러나서 하나하나씩 변수를 통해 props로 넘겨줘야 했다.
const ModalOverlay: React.FC<Props> = ({ title, children, onConfirm, value, onChange, onClose }) => {
return (
<S.Container>
<header>
<h2>{title}</h2>
</header>
<main>
<p>{children}</p>
<LoginInput value={value} onChange={onChange} />
</main>
<footer>
<Button btnType="highlighted" onClick={onClose}>
취소
</Button>
<Button btnType="highlighted" onClick={onConfirm}>
확인
</Button>
</footer>
</S.Container>
)
}
마지막으로, portal로 modal 구현하는 과정에서 젤 중요한 부분!
위에서 구현했던 두 컴포넌트에 각각createPortal()
을 적용해준다.
// portal을 이용한 모달 기능 구현
import React from "react"
import ReactDOM from "react-dom"
// props type은 ModalOverlay 컴포넌트에서 사용하는 props 종류와 같으므로 전에 선언해주었던 Props 타입을 재사용하면 된다!
const Modal: React.FC<Props> = ({ title, children, onConfirm, value, onChange, onClose }) => {
return (
<div>
{ReactDOM.createPortal(
<Backdrop onClose={onClose} />,
document.getElementById("backdrop-root") as HTMLElement
)}
{ReactDOM.createPortal(
<ModalOverlay
title={title}
onConfirm={onConfirm}
value={value}
onChange={onChange}
onClose={onClose}
>
{children}
</ModalOverlay>,
document.getElementById("overlay-root") as HTMLElement
)}
</div>
)
}
export default Modal;
윗 부분 구현하던 도중에
document.getElementById()
부분에서 에러 발생 😵💫
👀 해결 방법:document.getElementById("backdrop-root") as HTMLElement
요렇게 뒤에 as를 통해 타입을 지정해주니까 에러 해결~!
요렇게 구현하면 모달 구현 끄읏!
- 아래 화면과 같이 모달이 닫혔을 땐
backdrop-root
&overlay-root
에 요소가 없다가 모달이 열리면 요소가 추가되는 것을 확인할 수 있다.- 다른 컴포넌트들이 렌더링되는 일반 root에 생기는 것이 아니라 따로 설정해둔
backdrop-root
&overlay-root
에 요소가 추가되는 것이 중요!!
생각보다 타스로 변경하면서 에러도 많이 나고, 모달 내에 input도 넣어야 해서 수월하진 않았지만 직접 구현해보면서 portal 적용법을 알 수 있었다. 🙌