[ReactDom Portal] 모달 창이 전역적 위치에서 렌더링되도록 만들기

GY·2022년 1월 14일
0

리액트

목록 보기
39/54
post-thumbnail

2020.02.12
이후에 프로젝트를 하면서 모달창을 다시 만들 기회가 생겼다.
이 포스팅에서 작성한 코드에서 수정해 조금 다르게 만들었다.
👉포스팅 보러가기

내 모달창, 이대로 괜찮을까?

nav컴포넌트의 버튼을 클릭하면 모달창이 떠야했다.
그런데 모달창이 뜨는 위치에 대한 고민이 생겼다.
별도로 모달창 컴포넌트를 만들었더라도 렌더링하는 위치를 지정해주어야 하는데,
실제로 모달창은 전역 위치에서 보여주는 것인데... 특정 컴포넌트의 하위에서 호출해 렌더링하는 것이 맞는 건지, 더 좋은 방법이 있을지 궁금했다.

그리고 찾아보다가 ReactDom에서 제공하는 Portal이라는 기능을 발견했고 한번 사용해보기로 결정했다.



ReactDom Portal

1. 모달이 렌더링 될 위치 심어주기

부모 컴포넌트의 돔 계층구조 바깥에 있는 돔 노드로 자식을 렌더링하는 기능
즉, 외부에 존재하는 돔 노드가 안에 존재하는 것처럼 연결해주는 포탈 기능 제공

컴포넌트를 부모 컴포넌트의 계층 구조 바깥에 있는 돔노드로 자식을 렌더링해준다.

 <body>
  <div id="root"></div>
  <div id="modal"></div>
</body>

이 둘은 형제 관계처럼 보이지만 modal은 root내부에 있는 자식 컴포넌트이고, 렌더링만 portal을 통해 바깥에서 이루어지는 것이다.

param 1

컴포넌트, 요소: 렌더링 가능한 리액트 자식요소

param 2

컴포넌트를 넣어줄 DOM 요소

일반적으로 컴포넌트의 render 메서드에서 요소를 반환하면 가장 가까운 상위 노드의 자식으로 DOM에 마운트 된다.


2. Portal 만들기

import ReactDOM from 'react-dom';

function ModalPortal({ children }) {
  const el = document.getElementById('modal');
  return ReactDOM.createPortal(children, el);
}

export default ModalPortal;

ModalPortal이라는 이름으로 React.Portal을 생성하는 함수형 컴포넌트를 만들었다.

  • ModalPortal 컴포넌트의 하위에 위치하는 자식 컴포넌트는 children이라는 이름의 props로 전달된다.
  • reactDOM.createPortal 함수로 이 전달받은 컴포넌트를 포탈을 열듯 원하는 위치에 대신 렌더링 해준다. 이 함수는 2개 인자를 받는데, 첫번째 인자는 렌더링할 요소이고 두 번째 인자는 렌더링할 위치이다.
  • 즉, 이 함수는 createPortal로 포탈을 새로 생성하여 children이라는 이름으로 전달된 ModalPortal의 하위 요소를 el (앞에서 선언해준 root외 modal 이라는 클래스명을 가진 요소)에서 렌더링해준다.

3. Modal.js 만들기


  const modalTitle = {
    location: '어디로 떠날까요?',
    date: '언제 떠날까요?',
    date_detail: '예약 날짜를 선택해주세요.',
  };

  const modalButton = {
    location: 'SEARCH',
    date: 'SEARCH',
    date_detail: 'DONE',
  };

  const modalContent = {
    location: <Location />,
    date: <SelectDate />,
    date_detail: <SelectDate detail={true} />,
  };
  console.log(modalContent[showModal]);

  return (
    <Wrapper>
      <PopUp>
        <PopUpTitle>
          <h2>{modalTitle[showModal]}</h2>
          <GrClose onClick={onClose} />
        </PopUpTitle>
        <CalendarWrapper>{modalContent[showModal]}</CalendarWrapper>
        <SearchButton
          diabled={!buttonIsValid}
          validDates={validDates}
          onClick={onClickSearch}
        >
          {modalButton[showModal]}
          <BsArrowRight />
        </SearchButton>
      </PopUp>
    </Wrapper>
  );
}

4. 모달을 띄울 컴포넌트에 portal, modal 조건부 렌더링

<ModalPortal>
  <Modal />
</ModalPortal>

실제로는 nav 컴포넌트에서 위 코드를 작성했는데, ModalPortal이라는 포털로 인해 하위에 위치한 Modal 컴포넌트는 Nav컴포넌트 하위가 아닌, 모든 리액트 컴포넌트들이 계층구조를 갖고 렌더링되는 root 요소도 아닌,그 옆에 위치한 modal이라는 클래스명을 가진 요소에 렌더링된다.

실제로 개발자도구에서 확인해보자

이렇게 root가 아닌 modal 요소에서 독립적으로 렌더링되는 것을 볼 수 있다.

물론, 실제로는 Nav컴포넌트 하위에 위치해 있으므로 기존과 동일하게 props를 내려받는 등의 동작을 할 수 있다.

고민 포인트: Modal, ModalPortal 위치

렌더링을 전역적으로 하도록 만들었더라도, Nav컴포넌트에서 이 ModalPortal과 Modal 컴포넌트를 호출하는 것이 적절할까? 라는 생각이 들었다.

  1. 해당 Modal이 어디에서 코드가 작성되었는지를 잘 찾을 수 있을까?
  • 수정 등의 이유로 ModalPortal과 Modal컴포넌트를 호출하는 위치를 찾아야 할 때, 모든 파일을 뒤져보지 않는 한 Nav 컴포넌트 하위에 관련 코드가 작성되었다는 것을 알 수 있을까? 그렇지 않다면, 이 위치가 적절하다고 할 수 있을까?

리팩토링 하면서 이 부분에 대해서는 다시 고민해봐야겠다.


Reference

profile
Why?에서 시작해 How를 찾는 과정을 좋아합니다. 그 고민과 성장의 과정을 꾸준히 기록하고자 합니다.

0개의 댓글