young
is this it
young
전체 방문자
오늘
어제
  • 분류 전체보기 (143)
    • 웹_프론트엔드 (1)
      • 로드맵 챌린지 (73)
      • Svelte (2)
      • React (6)
      • JavaScript (8)
      • TypeScript (2)
      • HTML+CSS (5)
    • 웹_백엔드 (0)
      • Django (0)
    • 빅데이터 (33)
      • R (30)
      • Python (2)
    • 기타 (11)
      • git (3)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • owasp
  • ssl
  • 태스크러너
  • bem
  • 인증
  • 암호화
  • 공개키
  • ggplot
  • form
  • vcs
  • 구글맵api
  • css후처리기
  • Regex
  • 버전관리
  • css네이밍
  • 웹보안
  • 보안취약점
  • 대칭키
  • ggmap()
  • rstudio지도정보

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
young

is this it

웹_프론트엔드/React

[리액트] 컴파운드 컴포넌트 패턴으로 작성하는 모달 컴포넌트 (+ dialogue 태그 활용)

2023. 9. 18. 16:11
반응형

문제 발견: 기존 모달 컴포넌트의 중복 코드 양산

리액트 프로젝트에서 모달 컴포넌트를 만들어 두고 사용할 때마다 매번 아래처럼 모달 상태를 위한 코드들이 양산되었다.

// 모달을 보여줄 것인지 아닌지의 boolean 상태
const [show, setShow] = useState(false);

// 필요시 모달 상태 핸들러까지...
const modalHandler = () => {
        setShow((prev: boolean) => !prev);
};

return (
    <Modal isOpen={show} onClose={modalHandler}>
        모달 내용(children)
    </Modal>
)

위처럼, Modal 컴포넌트로 props를 내려주기 위해서 모달을 사용하는 상위 컴포넌트마다 모달 상태 관련 코드를 중복 작성해야 하는 것이다. 이런 컴포넌트 작성 패턴을 '렌더 프롭스 패턴(Render Props pattern)'라고 하는데, 모달처럼 하위 컴포넌트를 다양하게 사용해야하는 경우에는 비효율적이다. 이런 경우에 필요한 패턴이 '컴파운드 컴포넌트 패턴(Compound component pattern)'이다.

 

컴파운드 컴포넌트 패턴의 키 포인트

1. React Context API 사용한 상태 관리

2. (Context API가 감싸고 있는) 부모 컴포넌트

3. (외부 진입 버튼 + 모달 내용) 자식 컴포넌트를 부모 컴포넌트의 property로 할당

4. 자식 컴포넌트에서 리액트 최상위 API인 cloneElement()를 활용하여 외부로 onClick 이벤트 전달

 

모달 컴포넌트 작성

"use client";
import {
  cloneElement,
  createContext,
  Dispatch, MouseEvent,
  ReactElement,
  ReactNode,
  SetStateAction,
  useContext, useRef,
  useState
} from "react";
import { createPortal } from "react-dom";

interface IModalContext {
  showModalName: string;
  setShowModalName: Dispatch<SetStateAction<string>>;
  close: () => void;
}

const ModalContext = createContext<IModalContext | undefined>(undefined);

// 모달 부모 컴포넌트
function Modal({ children }: { children: ReactNode }) {
  const [modalName, setModalName] = useState<string>("");
  const close = () => setModalName("");

  return (
    <ModalContext.Provider value={{ showModalName: modalName, setShowModalName: setModalName, close }}>
      {children}
    </ModalContext.Provider>
  );
}

// 모달 자식 컴포넌트 Open, Window
function Open({ children, modalName }: { children: ReactNode, modalName: string }) {
  const { setShowModalName } = useContext(ModalContext)!;
  return cloneElement(children as ReactElement, { onClick: () => setShowModalName(modalName) });
}

function Window({ children, modalName }: { children: ReactElement; modalName: string; }) {
  const { showModalName, close } = useContext(ModalContext)!;
  const ref = useRef<HTMLDialogElement>(null);
  const onClose = (e: MouseEvent) => {
    if (e?.target === ref?.current) {
      close();
    }
  };

  if (modalName === showModalName) {
    ref?.current?.showModal();
  } else {
    ref?.current?.close();
  }

  return createPortal(
    <dialog
      ref={ref}
      onClick={onClose}
      className={"w-full max-w-[300px] backdrop-opacity-75 bg-grey-200 rounded-[20px]"}>
      <div>
        <button onClick={close}>닫기</button>
        <div>{cloneElement(children as ReactElement, { onCloseModal: close })}</div>
      </div>
    </dialog>,
    document.body
  );
}

Modal.Open = Open;
Modal.Window = Window;

export default Modal;

 

상위 컴포넌트에서 모달 컴포넌트 사용하기

export default function App() {
  return (
    <div className="App">
      <Modal>
        <Modal.Open modalName="modal1">
          <button>모달 열기 버튼</button>
        </Modal.Open>
        <Modal.Window modalName="modal1">
        	<>모달 내용 작성</>
        </Modal.Window>
      </Modal>
    </div>
  );
}

 

 

 

 

반응형

'웹_프론트엔드 > React' 카테고리의 다른 글

[리액트] gh-pages 배포 후 새로고침시 404 오류  (1) 2024.03.17
[리액트] react-daum-postcode 우편번호 검색 컴포넌트 예시  (0) 2023.11.23
[JSX] 조건에 따른 JSX 작성 패턴 - if, switch, object 구조  (0) 2023.07.19
리액트 JSX/TSX에서 switch 문법 사용하기  (0) 2023.02.03
[React.ts] props로 setState 넘길 때 타입 지정하는 방법  (0) 2022.12.19
    '웹_프론트엔드/React' 카테고리의 다른 글
    • [리액트] gh-pages 배포 후 새로고침시 404 오류
    • [리액트] react-daum-postcode 우편번호 검색 컴포넌트 예시
    • [JSX] 조건에 따른 JSX 작성 패턴 - if, switch, object 구조
    • 리액트 JSX/TSX에서 switch 문법 사용하기
    young
    young

    티스토리툴바