코드노트

리액트 createPortal로 DOM추가하기 본문

Code note/리액트

리액트 createPortal로 DOM추가하기

코드노트 2023. 8. 1. 23:31

createPortal

ReactDOM.createPortal(child, container)

- createPortal은 리액트에서 DOM트리  <div id="root"> 의 다른 위치에 컴포넌트를 렌더링을 할 수 있는 기능

- 기본적으로 컴포넌트는 컴포넌트 트리의 노드로 렌더링이 되지만, 컴포넌트를 루트 DOM요소 밖의 다른 위치에 렌더링해야 할때 사용할 수 있다.

- 간단하게 말하면 첫번째 인자는 자식요소, 두번째 인자는 자식요소를 렌더링할 DOM요소

ex) 모달, 툴팁, 독립적인 컴포넌트 등등

 

 

이번 프로젝트에서 모달을 사용하면서 css를 사용하여 z-index를 조절하여 모달을 만들었는데

createPortal을 사용하는것이 좋다고 한다. 아니 맞는것 같다.

- 모달은 기존 컴포넌트 구조와 격리되어야한다.

- 컴포넌트와 상호작용하는 문제가 발생할 수 있다.

 

 

이 외에도 kakaomap API를 테스트 하면서 map 안에서 표시되는 마커를 생성할 때에도 createPortal을 사용했었다.

그때도 지도에 따로 접근하고 그 접근한 마커를 동적으로 제어할때 문제가 생겨서

독립적으로 생성하고 표시하였는데 모달에도 적용하고 코드를 수정하는게 맞는것 같다. 

 

 

사용방법

<html lang="en" className={openSans.className}>
  <body>
    <div id="portal" />
  </body>
</html>

- getElementById를 사용하여 DOM을 추가할 것이기 때문에 id를 가진 div요소를 추가한다.

 

// ModalPoralComponent.tsx

import { useEffect } from "react";
import reactDom from "react-dom";

type Props = {
  children: React.ReactNode;
};

export default function ModalPortal({ children }: Props) {
  const el = document.getElementById("portal") as Element;
  return reactDom.createPortal(children, el);
}

- 이렇게 컴포넌트로 만들어서 사용하면 재사용에 있어서도 좋은것 같다.

- 모달로 사용할 컴포넌트를 이제 ModalPortal의 children으로 넣어주면 쉽게 사용할 수 있다.

 

 

 

- 여기서 만난 문제 모달을 사용하게 되면 만약 스크롤이 있다면 모달 뒤에 화면에서도 스크롤이 진행된다.

- 모달을 가장 위로 보여주고 뒤에 있는 스크롤을 막고 싶다면 window에서 제어할 요소를 찾아서 style을 조절해주자.

 

 

  useEffect(() => {
    const originalStyle = window.getComputedStyle(document.body).overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = originalStyle;
    };
  }, []);

  if (typeof window === "undefined") {
    return null;
  }

- useEffect를 사용하여 마운트되면 getComputedStyle 메서드를 사용하여 overflow 스타일 속성을 hidden으로 설정하여 스크롤을 막아줄 수 있다.

 

 

스크롤 방지 적용 코드

import { useEffect } from "react";
import reactDom from "react-dom";
type Props = {
  children: React.ReactNode;
};
export default function ModalPortal({ children }: Props) {
  useEffect(() => {
    const originalStyle = window.getComputedStyle(document.body).overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = originalStyle;
    };
  }, []);

  if (typeof window === "undefined") {
    return null;
  }
  const el = document.getElementById("portal") as Element;
  return reactDom.createPortal(children, el);
}

 

 

 

 

createPortal을 사용하는 경우는 생각보다 많이 있는것 같다.

DOM을 직접적으로 따로 생성하는 작업들은 많이 하지 않았었는데

모달, 팝업, 알림창과 같은 독립적으로 사용되는 컴포넌트를 만들때 편하게 사용할 수 있다.

컴포넌트 재사용 뿐만 아니라 다른 코드에서도 활용할 수 있는곳에서 활용을 할 수 있도록 해봐야겠다.