Portals 는 리액트 프로젝트에서 컴포넌트를 렌더링하게 될 때, UI 를 어디에 렌더링 시킬지 DOM 을 사전에 선택하여 부모 컴포넌트의 바깥에 렌더링 할 수 있게 해주는 기능입니다. Portals를 사용하면 DOM의 계층구조 시스템에 종속되지 않으면서 컴포넌트를 렌더링 할 수 있습니다.
기본 설정으로 Next.js는 id 값이 __next
인 DOM에 MyApp 컴포넌트를 렌더링합니다.
우리는 React Portals를 사용하여 __next
DOM이 아닌 다른 계층의 DOM에 모달을 렌더링해야하므로 _document.js
파일의 <Main />
하단에 <div id='modal'></div>
를 추가해줍니다.
import Document, { Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
...
render() {
return (
<html>
<Head>
<meta charSet='utf-8' />
<meta
name='viewport'
content='initial-scale=1.0, width=device-width'
/>
{this.props.styles}
</Head>
<body>
<Main /> // MyApp 컴포넌트가 렌더링되는 DOM
<div id='modal'></div> // Modal을 렌더링 할 새로운 DOM하나를 만들어줍니다.
<NextScript />
</body>
</html>
);
}
}
import ReactDOM from 'react-dom';
import { memo } from 'react';
const ModalPortal = ({ children }) => {
const el = document.getElementById('modal');
return ReactDOM.createPortal(children, el);
};
export default memo(ModalPortal);
모달을 적용하고 싶은 컴포넌트를 위에서 만든 ModalPortal 컴포넌트로 감싸줍니다.
import React from 'react;
const SetModal = () => {
return (
{onModal ? (
<ModalPortal>
<Modal>
<FollowList followData={followData} />
</Modal>
</ModalPortal>
) : null}
)
}
export default SetModal;
모달을 스크롤 되는 페이지에 추가했을 때, 모달이 떠있음에도 불구하고 스크롤이 동작한다면 다음과 같이 해주면 된다.
useEffect(() => {
// 모달이 떴을 때 뒤의 영역 스크롤 고정
document.body.style.cssText = `position: fixed; top: -${window.scrollY}px`;
return () => {
const scrollY = document.body.style.top;
document.body.style.cssText = `position: ""; top: "";`;
window.scrollTo(0, parseInt(scrollY || '0') * -1);
};
}, []);
cssText를 쓰는 이유는 style을 여러번 접근하면 그 횟수만큼 reflow가 발생하게 됩니다. cssText를 이용하면 1번만 계산하기 때문에 이렇게 js로 css를 건드릴 경우 퍼포먼스를 위해 필수로 해주시는게 좋습니다. (class 명을 추가해줘도 됩니다.)