yehey's 공부 노트 \n ο(=•ω<=)ρ⌒☆

[Next js] React Portal 적용하기 본문

개발

[Next js] React Portal 적용하기

yehey 2024. 1. 2. 07:29

Today I Learned

: React Portal 을 왜 쓰는지, 썼을 때의 장점을 알아보고 Next js 프로젝트에 적용해서 모달 컴포넌트를 개선했다!!

배경

우연한 계기로 ReactDOM Portal 을 접하게 되었다. 거의 처음 보는거라 이게 무슨 역할을 하는지 궁금했고 이후 찾아봤다. 서치 결과 장점이 많아서 사용해보지 않을 이유가 없었다!

Contents

React Portal 은 다양한 이유에서 사용하는 듯 했다. 우선 이해한 바 로는 '현재 컴포넌트를 다른 DOM으로 분리할 때, 다른 DOM에 렌더링 하고자 할 때 사용한다.' 가 첫번째였다. DOM 을 분리하는게 무슨 이점이 있는지 궁금했지만 곧바로 이해할 수 있었다. Portal 없이 컴포넌트, 페이지를 개발하는 과정에서는 계층적으로 컴포넌트를 구성하기에 부모 자식 관계에 영향을 많이 받는다. 이러한 영향을 최소화 하거나, 영향을 받지 않도록 하기 위해 사용하는 것으로 우선 이해했다. (portal은 body 태그 바로 아래 위치하기 때문)

가장 많이 볼 수 있는 사용 예는 drawer 나 modal 이었고, 마침 진행중인 프로젝트에도 modal 과 drawer를 사용하고 있어서 portal을 적용해서 개선작업을 시작했다.

Portal을 적용하려는 이유는 위에서 말했 듯이 계층적 구조에서 부모 컴포넌트에 영향 받는걸 최소화하기 위해서였다. 추가로 적용된 사례를 보니 생성되는 html 코드가 훨씬 간결해지고 보기 좋아졌다는 효과도 있었다. 당장 적용해보자

제일 만만한 Modal 에 적용해보기로 했다.

개선 전 생성되던 코드는 다음과 같았다.

Modal 컴포넌트를 찾으려면 관련된 부분까지 내려가야한다. 디버깅에도 좋지 않다. 실제로 Modal 컴포넌트가 여러 Wrapper 안에 존재하기 때문에 스타일링에서 약간의 이슈가 있었다. 감싸고 있는 Wrapper 중 하나에서 css filter 로 그림자를 그리고 있는데, 이 때문에 wrapper 크기가 제한되었다.

더보기

요소를 일반적인 문서 흐름에서 제거하고, 페이지 레이아웃에 공간도 배정하지 않습니다. 대신 뷰포트의 초기 컨테이닝 블록을 기준으로 삼아 배치합니다. 단, 요소의 조상 중 하나가 transform, perspective, filter 속성 중 어느 하나라도 none이 아니라면 (CSS Transforms 명세 참조) 뷰포트 대신 그 조상을 컨테이닝 블록으로 삼습니다.

 

https://developer.mozilla.org/ko/docs/Web/CSS/position#fixed

여기서 말하고 싶은건, Modal 은 다양한 곳에서 쓰이는 만큼 상위 컴포넌트로부터 받는 영향을 최소화, 혹은 없애야 한다고 생각했다. Portal 적용하기 딱 좋다.

Practice: Modal 컴포넌트를 Portal로 분리하기

우선 portal이 될 부분을 정해줘야한다. page router 까지는 _document.tsx 에 관련 portal 부분을 정의해줬지만, app router 부터는 app/layout.tsx 에 정의해줄 수 있다.

// app/layout.tsx
export default function RootLayout({ children }: IRootLayoutProps) {
  return (
    <html lang="kr">
      <body>
          <div id="portal" />
          {children}
      </body>
    </html>
  );
}

 

이제 portal 컴포넌트를 추가한다.

import { ReactNode } from 'react';
import ReactDOM from 'react-dom';

interface IPortalProps {
  children: ReactNode;
}

function Portal({ children }: IPortalProps) {
  const element = typeof window !== 'undefined' && document.querySelector(`#portal`);

  return element && children ? ReactDOM.createPortal(children, element) : null;
}

export default Portal;

 

사용 방법은 간단하다. <div id='portal'></div> 안에, 즉 포탈에 넣고 싶은 컴포넌트는 Portal 컴포넌트로 감싸주기만 하면 된다.

function Modal({ onClose, onComplete, children, title, buttonLabel }: IModalProps) {
  return (
    <Portal>
      <Overlay onClick={onClose}>
        <ModalWrapper onClick={handleWrapperClick}>
          <Title>{title}</Title>
        </ModalWrapper>
      </Overlay>
    </Portal>
  );
}

 

Modal 이 열리면 지정해둔 포탈 아래 Modal 컴포넌트가 위치한다. 이제 Modal 을 찾으러 다른 곳 깊이 가지 않아도 된다. 

 

추가로 위에서 언급했던, filter 관련 문제도 해결되었다. Modal 컴포넌트는 더이상 filter가 적용된 Wrapper에 영향을 받지 않기 때문에. Portal 로 다른 곳에서 생성되기 때문에 같이 해결되었다. (원래 box-shadow 로 해결했는데, 혹시 되나 싶어서 filter로 수정했는데 너무 잘된다 매우 기쁨)

 

before / after

 

Learned

ReactDOM Portal을 Next에 적용해서 Modal 을 만들어보았다. 그동안 어디에서 구현해야하나 고민하고 상위 컴포넌트에 영향을 받는 경우가 많은 것도 있었는데, Portal을 적절한 곳에 사용하면 해결될 수 있을 것 같다. 이제 Portal 이용해서 Drawer 만들어야지...


What I Will Study

<div id="portal"> 태그 하나에 portal 컴포넌트들이 렌더링되는게 맞는지 조금 고민이 된다. 동시에 여러개를 포탈에서 렌더링하게된다면 어떤 일이 벌어질 지. 가능하다면 역할마다 id를 나누고 싶지만 그건 더 복잡하게 만드는 일 같기도 하다. Portal 을 잘 사용하고 있는 프로젝트나 블로그 찾아보고 답을 적용해봐야지..

참조

https://react.dev/reference/react-dom/createPortal

 

createPortal – React

The library for web and native user interfaces

react.dev

https://velog.io/@choiminwoo2/Next.js%EC%97%90%EC%84%9C-React-portal-%EC%82%AC%EC%9A%A9#1-%EC%B5%9C%EC%83%81%EC%9C%84%ED%8F%B4%EB%8D%94_documenttsx

 

Next.js에서 React portal 사용

Next.js에서 ReactPortal을 사용하기 위해서는 \_app 이후에 동작하는 \_document을 작성해야 합니다. \_document는 공통적으로 활용할 <head> (Ex. 메타 태그)나 <body> 태그 안에 들어갈 내용들을 커스텀할때 활

velog.io

https://kyounghwan01.github.io/blog/React/next/use-portal/#next%E1%84%8B%E1%85%A6%E1%84%89%E1%85%A5-portal-%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5

 

Portal 사용법 (next와 cra)

Portal 사용법 (next와 cra), react, seo, ssr, getStaticProps, getStaticPaths, getServerSideProps

kyounghwan01.github.io

 

Comments