솔미는 성장중

Layout(모달, 바텀시트) 공통 컴포넌트를 만드는 여러가지 방법 💫 본문

카테고리 없음

Layout(모달, 바텀시트) 공통 컴포넌트를 만드는 여러가지 방법 💫

solming 2024. 3. 18. 19:23
728x90

 WHY? (문제점 / Layout 공통 컴포넌트 리팩토링을 결심한 계기) 

믹스패널 트래킹 이벤트를 달기 위해 모달/바텀시트 컴포넌트를 정리하던 중 여러 방법으로 관리되고 있는 것을 발견
  • 열고 닫는 방식 (useState vs useRecoilState)
  • 띄우는 위치 (페이지 내 vs 전역(_app))
  • FullPageModal & BottomSheet ➝ 상위에서 하나로 묶어주는 공통 컴포넌트 부재
  • Portal 사용 여부
  • 사용하지 않는 모달/바텀시트가 관리되지 않고 있음

현재 Modal을 사용하는 코드를 JSX에 넣어놓고 함수를 이용하여 open, close를 관리하고 있음.
➝ JSX의 가독성도 떨어지고 Modal이 언제 open이 되는지 신경 써서 코드를 읽어야 함.
➝ state 관리도 신경 써야함.
언제 Modal을 open 하여 사용하는지 직관적으로 보이는 코드로 변경하고자 함.

 

고려사항 (필수적으로 만족해야 하거나 고려되어야 하는 사항들) 

  1. z-index 관리가 가능해야 함 (=각 레이어가 한 가지 z-index로 고정되면 안된다.)
  2. 믹스패널 트래킹 이벤트를 달기 용이해야 한다.
  • = 공통 컴포넌트가 존재해야 한다.
  • cf. 믹스패널 트래킹 이벤트 = open, close, buttonClick 등
  1. 특정 페이지에서만 뜨는 모달, 여러 페이지에서 뜨는 모달이 있는데 이를 분리해서 관리하는 게 나을지, 하나의 방법으로 통일하는게 나을지에 대한 고려 필요.
  2. 여러 개의 모달/바텀시트를 띄울 수 있어야 한다.

 

 HOW? 

1안) ContextAPI - map 메소드 사용

참고링크

요약
  • ModalsProvider 내에 openedModals [] state 생성
  • 담기는 데이터는 '컴포넌트 & 해당 컴포넌트에 건네줘야하는 props'
// ModalsProvider 일부 - Provider & open, close 함수 선언
const [openedModals, setOpenedModals] = useState([]);

const open = (Component, props) => {
//mixpanel tracking
    setOpenedModals((modals) => {
      return [...modals, { Component, props }];
    });
  };
​
  const close = (Component) => {
    setOpenedModals((modals) => {
      return modals.filter((modal) => {
        return modal.Component !== Component;
      });
    });
  };
​
// useModal - 사용하기 쉽게 만들어주는 커스텀 훅
  const { open, close } = useContext(ModalsDispatchContext);
​
  const openModal = (Component, props) => {
    open(Component, props);
  };
​
  const closeModal = (Component) => {
    close(Component);
  };
​
 // A.tsx - 사용 예시 (openModal로 배열에 추가)
 const { openModal } = useModals();
​
  const handleClick = () => {
    openModal(MyModal, { foo: 'bar' });
  };

// 모달 뿌려주는 역할 
const Modals = () => {
  const openedModals = useContext(ModalsStateContext);
​
  return openedModals.map((modal, index) => {
    const { Component, props } = modal;
​
    return <Component key={index} {...props} />;
  });
};
​
export default Modals;

장점
  • 요구 조건 만족
    • open, close에 트래킹 이벤트 달기
    • 배열 내 순서로 레이어 높이 보장 (z-index 문서화 불필요)
  • 전역 상태 관리 라이브러리 의존도 낮출 수 있음
  • aysnc, await로 비동기도 문제 없음.
단점
  • unmount 애니메이션이 제대로 동작하지 않을 수 있다.
    framer motion의 AnimationPresence로 감싼다하더라도 배열 자체가 바뀐걸 인지하기 어려울 것이라고 예상된다.

 

2안) ContextAPI 사용 - overlay-kit

ContextAPI 사용 시 단점: 페이지 리렌더링
리렌더링을 최소화하는 방법으로 observer pattern이 존재한다.
 

3안) observer pattern

 

 그 외 논의사항 

Portal 을 사용할 것인가?

  • portal 관련 정리가 잘 된 글
  • 부모에 overflow:hidden, z-index가 있는 경우에도 영향을 안 받을 수 있다.
  • 이벤트 전파 관련한 문제가 생기지 않도록 react 트리 구조상 처리가 되어있다
  • 공식문서 )
    "portal의 전형적인 유스케이스는 부모 컴포넌트에 overflow: hidden이나 z-index가 있는 경우이지만, 시각적으로 자식을 '튀어나오도록' 보여야 하는 경우도 있습니다. 예를 들어, 다이얼로그, 호버카드나 툴팁과 같은 것입니다."
  • 바텀시트, 모달은 특정 페이지 내에서 뜨는 것인데, 밖으로 빠져있는 것이 트리구조상 적절한 위치에 위치한 것이 아니라는 의견도 있으나 개인적인 생각으로는 Layer 특성상 안정성을 보장하는 것이 더 좋다고 생각한다.

따라서 사용하지 않을 이유가 없어보인다. 따라서 사용하도록 결정.

dialog 태그 사용

  • 호환성 상관없이 사용가능
  • 웹 표준
  • framer-motion으로 버벅이는 애니메이션을 css animation으로 손쉽게 변경 가능
  • backdrop도 지원
::backdrop
dialog의 ::backdrop 의사 요소를 사용해 background-color의 alpha 값 0.8
[open]
dialog가 open(열림) 상태 일 때의 css.
예시에서는 모달창이 열리면 위에서 아래로 내려오는 애니메이션을 추가했다.
 

필요없어진 모달/ 바텀시트

  • 대다수는 그대로 사용하지 않을 것으로 예상되어 로직도 함께 삭제하기로 결정됐었음
    • 지우면서 commit에 garbage 태그 달기
  • 다른 좋은 관리법이 있을지?에 대해 가볍게 얘기 나눠보기
728x90