W2 - 기술정리 | React Portals

yisu.kim·2021년 8월 8일
2

Pre Onboarding

목록 보기
5/16
post-thumbnail

Portals

Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.

by ReactJS

부모 컴포넌트와 자식 컴포넌트라는 관계는 유지되지만 자식 컴포넌트의 렌더링은 부모 컴포넌트 밖에 있는 DOM 노드에서 일어나도록 하는 기능이다. 즉 렌더링이 DOM 트리 계층 구조로부터 자유로워진다.

왜 Portals인가?

portals의 일반적인 사용 사례는 다음과 같다.

A typical use case for portals is when a parent component has an overflow: hidden or z-index style, but you need the child to visually “break out” of its container. For example, dialogs, hovercards, and tooltips.

사이트를 구성하다보면 흔히 다이얼로그, 호버카드, 툴팁, 채팅 위젯, 모달, 로더 등 사용자에게 시각적으로 튀어나오도록 보여야할 때가 있는데 이런 경우에 portals를 활용할 수 있다.

Portals를 활용해 모달을 구현해보자

📦 codesandbox에서 실습이 가능합니다.

CRA로 프로젝트를 만들면 public 폴더의 index.html 파일에 기본적으로 아래와 같이 작성되어 있다. 이때 #root는 일반적으로 내가 작성한 App 컴포넌트가 렌더링되는 곳이다. 여기에 형제 노드인 #modal-root를 만들어 Modal 컴포넌트를 렌더링할 위치를 잡아준다.

<html>
  <head>
    ...
  </head>
  <body>
    <div id="root"></div>
    <div id="modal-root"></div>
  </body>
</html>

리액트의 createPortal을 호출하여 Portal을 생성할 수 있다. 첫 번째 인자(child)는 엘리먼트, 문자열 등 렌더링할 수 있는 React 자식이고, 두 번째 인자(container)는 렌더링할 위치인 DOM 엘리먼트이다.

ReactDOM.createPortal(child, container);

위 문법을 이용해 Modal 컴포넌트를 만들어보자. onClose() props는 버튼 클릭시 모달을 닫기 위해 부모 컴포넌트에서 넘겨주는 함수이다.

export default function Modal({ children, onClose }) {
  return ReactDom.createPortal(
    <div className="modal">
      <div className="contents">
        <h2>Modal Title</h2>
        {children}
        <button onClick={onClose}>close</button>
      </div>
    </div>,
    document.getElementById("modal-root")
  );
}

이제 모달을 사용하고 싶은 컴포넌트에 추가해보자. 모달을 보여줄지 숨길지 상태를 관리하기 위해 showModal state를 선언하고, 이 상태를 핸들링하는 함수 openModal()closeModal()을 각각 작성한다.

export default function App() {
  const [showModal, setShowModal] = useState(false);

  const openModal = () => {
    setShowModal(true);
  };

  const closeModal = () => {
    setShowModal(false);
  };

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <button onClick={openModal}>Show Modal</button>
      {showModal && (
        <Modal onClose={closeModal}>
          <p>Hi! I'm Modal.</p>
        </Modal>
      )}
    </div>
  );
}

BAAM!

개발자 도구를 살펴보면 #root가 아닌 #modal-root에 모달이 렌더링된 것을 확인할 수 있다.

분명 다른 DOM 트리에 렌더링되었는데 state가 바뀌고 이벤트가 정상적으로 동작하네? 여기서 다시 한번 React 공식 문서를 살펴보자.

Even though a portal can be anywhere in the DOM tree, it behaves like a normal React child in every other way. Features like context work exactly the same regardless of whether the child is a portal, as the portal still exists in the React tree regardless of position in the DOM tree.
This includes event bubbling. An event fired from inside a portal will propagate to ancestors in the containing React tree, even if those elements are not ancestors in the DOM tree.

by ReactJS

Portals 내부의 모달에서 발생한 클릭 이벤트는 DOM 트리와 상관없이 React tree에 포함된 상위로 전파된다. 아래 코드처럼 App 컴포넌트의 클릭 이벤트를 콘솔에 찍도록 하면 Modal 컴포넌트의 클릭 이벤트 버블링이 발생하는 것을 확인할 수 있다.

<div className="App" onClick={() => console.log("clicked!")}>
  ...
  <Modal/>
  ...
</div>

참고자료

잘못된 부분에 대한 지적은 언제든 환영합니다! 😉

0개의 댓글