TIL no.69 - React Modal(portal, react-transition-group)

김종진·2021년 11월 13일
0

React

목록 보기
17/17
post-thumbnail

React Modal

Modal은 웹에서 상당히 많이 사용되는 UI인데 기존에는 Modal 컴포넌트를 position: absolute로 띄어 구현했는데 여러 군데에서 컴포넌트를 재활용함에 있어 다른 엘리먼트의 z-index가 더 높거나 또는 부모 엘리먼트의 스타일을 상속받아 예상 밖으로 UI가 무너지는 상황을 종종 만나게 되었다.

이런 상황을 피하기 위해 React Portal을 활용하여 더 유연한 Modal 컴포넌트를 구현하였다.

React Portal

부모 컴포넌트 바깥에 있는 DOM 노드로 자식을 렌더링할 수 있게 해주는 기능이다

ReactDOM.createPortal(child, container)
child는 엘리먼트, 문자열, 혹은 fragment와 같은 어떤 종류이든 렌더링할 수 있는 React 자식,container는 DOM 엘리먼트이다.

부모 컴포넌트 바깥에 렌더링이 되기 때문에 부모 스타일 상속이나 z-index에 대한 영향을 피할 수 있다.

★ 위치는 부모 바깥이지만 모든 다른 면에서 일반적인 React 자식처럼 동작한다. (context, props, 이벤트 버블링)

public/index.html

(...)

<body>
    <div id="root"></div>
    <div id="modal-root"></div> // 모달이 실제로 렌더링 될 곳
</body>

src/component/Portal.tsx

import React from 'react';
import { createPortal } from 'react-dom';

const Portal: React.FC<Props> = ({ children }) => {
  const rootElement = document.getElementId('#modal-root');

  return (
    <>
      {rootElement ? createPortal(children, rootElement) : children}
    </>
  )
}

export default Portal;

Modal 컴포넌트
src/components/Modal/index.tsx

import React from 'react';
import styled from '@emotion/styled/macro';

import './index.css';
import Portal from './Portal';

(...styled-component)

interface Props {
  isOpen: boolean;
  onClose: () => void;
  selector?: string;
}

const Modal: React.FC<Props> = ({ children, onClose, isOpen, selector = '#modal-root' }) => (
  <>
  {
    isOpen ? (
     <Portal selector={selector}>
      <Overlay>
        <Dim onClick={onClose} />
        <Container>{children}</Container>
      </Overlay>
     </Portal>
    )
  }

  </>
)

export default Modal;

버튼을 누르면 Modal 컴포넌트가 Portal을 통해 index-html에서 지정한 Dom 위치에 렌더링된다.

개발자 도구에서 보면 전체 화면과 Modal 컴포넌트 위치가 분리된 것을 볼 수 있다.

Next에서 Portal

next는 html 파일이 없으므로 react 처럼 index.html에 div를 주입할 수 없기 때문에 _document.js파일에 div를 주입한다.

import React from "react";
import Document, { Html, Head, Main } from "next/document";

export default class MyDocument extends Document {
  render() {
    return (
      <Html>
        <body>
          <div id="portal" />
          <Main />
        </body>
      </Html>
    );
  }
}

React-transition-group

리액트의 컴포넌트에 transition 효과를 쉽게 줄 수 있는 공식 라이브러리, 페이지 간의 라우팅에서 transition 효과를 쉽게 줄 수 있다.

Modal을 띄움에 있어 적용해보도록 하자.

src/components/Modal/index.tsx

import { CSSTransition } from 'react-transition-group';

(...)

const Modal: React.FC<Props> = ({ children, onClose, isOpen, selector = '#modal-root' }) => (
  <CSSTransition in={isOpen} timeout={300} classNames="modal" unmountOnExit>
    <Portal selector={selector}>
      <Overlay>
        <Dim onClick={onClose} />
        <Container>{children}</Container>
      </Overlay>
    </Portal>
  </CSSTransition>
)

transition 효과를 줄 컴포넌트를 CSSTransition으로 감싸준뒤 속성을 부여한다.

in={isOpen} => 효과 스위치

className => 어떻게 꾸밀지 작성된 클래스

timeout => 작동시간

unmountOnExit => 애니메이션 종료 후 마운트를 해제하기 위한 설정
반대로 마운트 될때 옵션을 줄 수 있는 mountOnEnter도 있으며
둘다 기본값은 false이다.

Modal

src/components/Modal/index.css

.modal-enter {
    opacity: 0;
}

.modal-enter-active {
    opacity: 1;
    transition: opacity 300ms;
}

.modal-exit {
    opacity: 1;
}

.modal-exit-active {
    opacity: 0;
    transition: opacity 300ms;
}

이와같이 원하는 효과를 보여줄 CSS 속성값을 설정하면 적용이 가능하다.

profile
FE Developer

0개의 댓글