05. 공통 컴포넌트 만들기 - Modal

Gardener·2024년 5월 21일

NoColored

목록 보기
5/7
post-thumbnail

공통 컴포넌트를 미리 만들어놓는 것은, 통일성 있는 UI를 제공하기 위해 필수적인 부분이다.
공통 컴포넌트의 예시들로, 모달, 버튼, 입력 박스 등이 있겠지만, 그 중 가장 자주 쓰이는 Modal에 대해 알아보고자 한다.

전체 코드는 아래에 있지만, 내가 몰랐던 부분을 하나하나씩 알아보며 차근차근 같이 공부해볼 수 있도록 하자. 사실 이 게시물을 프로젝트를 하고 2달 반 정도 뒤에 작성하고 있는데, 공부하다보니 계속해서 새로운게 나온다.

import { ReactNode, useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
import * as styles from './index.css';

useRef : Dom 요소에 직접 접근할 수 있게 해주는 훅.
createPortal : React에서 포탈을 생성하여 컴포넌트 트리 외부의 DOM 노드로 자식을 렌더링할 수 있게 함.

const Modal = ({ children, isOpen, onClose }: Props) => {
  const ref = useRef<HTMLDialogElement>(null);

  useEffect(() => {
    if (isOpen) {
      ref.current?.showModal();
    } else {
      ref.current?.close();
    }
  }, [isOpen]);

  return createPortal(
    <dialog ref={ref} className={styles.modal} onClose={onClose}>
      <div className={styles.content}>{children}</div>
    </dialog>,
    document.getElementById('modal') as HTMLDivElement,
  );
};
  1. ref 를 정의 하는 부분
    : useRef 훅을 사용하여 'dialog' 요소에 대한 참조를 생성한다. 이 Type은 'HTMLDialogElement' 인데, html에서 사용되는 dialog 태그에 대한 Type이다.

  2. useEffect 훅을 사용 - 컴포넌트가 렌더링 후에 효과를 수행하게 해주는 useEffect 훅을 사용하여 isOpen 값이 변경될 때마다 실행되게 한다.

  3. createPortal 사용
    1) createPortal을 사용하여 모달을 특정 DOM 노드 ('modal' ID를 가진 요소) 로 랜더링함.
    2) 'dialog' 태그는 'ref'로 참조됨.
    3) {children} 은 'div' 요소 내부에 렌더링된다.

    이렇게 만들어진 Modal 컴포넌트는 useModal Hook을 통해 사용되어진다.

  // useModal Hook의 html 부분
  
    const Modal = ({ children }: Props) => {
    return (
      <ModalComponent isOpen={isOpen} onClose={closeModal}>
        {children}
      </ModalComponent>
    );
  };

이에서 'ModalComponent' 는 우리가 위에서 만든 const Modal을 선언함으로 연결된다. Modal: ModalComponent를 래핑하는 컴포넌트.

    return { Modal, openModal, closeModal, isOpen };
};

export default useModal;

이렇게 선언된 Hook은 아래와 같은 Return 값을 통해 사용될 수 있다.
아래는 사용한 예시이다.

const { Modal, openModal, closeModal } = useModal();

    <ColoredButton
      size='xsmall'
      text='바로가입'
      color='yellow'
      onClick={openModal}
    />
    <Modal>
      <SignUp closeModal={closeModal} />
    </Modal>

회원가입에서 위와 같은 예시를 통해 사용이 가능하다.

아래는 전체 구문.

//modal.tsx
import { ReactNode, useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';

import * as styles from './index.css';

interface Props {
  children: ReactNode;
  isOpen: boolean;
  onClose: () => void;
}

const Modal = ({ children, isOpen, onClose }: Props) => {
  const ref = useRef<HTMLDialogElement>(null);

  useEffect(() => {
    if (isOpen) {
      ref.current?.showModal();
    } else {
      ref.current?.close();
    }
  }, [isOpen]);

  return createPortal(
    <dialog ref={ref} className={styles.modal} onClose={onClose}>
      <div className={styles.content}>{children}</div>
    </dialog>,
    document.getElementById('modal') as HTMLDivElement,
  );
};

export default Modal;
  //useModal.tsx
  
import { ReactNode, useState } from 'react';

import ModalComponent from '@/components/Modal/index';

interface Props {
  children: ReactNode;
}

const useModal = () => {
  const [isOpen, setIsOpen] = useState(false);

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

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

  const Modal = ({ children }: Props) => {
    return (
      <ModalComponent isOpen={isOpen} onClose={closeModal}>
        {children}
      </ModalComponent>
    );
  };

  return { Modal, openModal, closeModal, isOpen };
};

export default useModal;
profile
영혼의 정원수

0개의 댓글