Notice
Recent Posts
Recent Comments
Link
솔미는 성장중
Layout(모달, 바텀시트) 공통 컴포넌트를 만드는 여러가지 방법 💫 본문
728x90
WHY? (문제점 / Layout 공통 컴포넌트 리팩토링을 결심한 계기)
믹스패널 트래킹 이벤트를 달기 위해 모달/바텀시트 컴포넌트를 정리하던 중 여러 방법으로 관리되고 있는 것을 발견
- 열고 닫는 방식 (useState vs useRecoilState)
- 띄우는 위치 (페이지 내 vs 전역(_app))
- FullPageModal & BottomSheet ➝ 상위에서 하나로 묶어주는 공통 컴포넌트 부재
- Portal 사용 여부
- 사용하지 않는 모달/바텀시트가 관리되지 않고 있음
현재 Modal을 사용하는 코드를 JSX에 넣어놓고 함수를 이용하여 open, close를 관리하고 있음.
➝ JSX의 가독성도 떨어지고 Modal이 언제 open이 되는지 신경 써서 코드를 읽어야 함.
➝ state 관리도 신경 써야함.
➝ 언제 Modal을 open 하여 사용하는지 직관적으로 보이는 코드로 변경하고자 함.
고려사항 (필수적으로 만족해야 하거나 고려되어야 하는 사항들)
- z-index 관리가 가능해야 함 (=각 레이어가 한 가지 z-index로 고정되면 안된다.)
- 믹스패널 트래킹 이벤트를 달기 용이해야 한다.
- = 공통 컴포넌트가 존재해야 한다.
- cf. 믹스패널 트래킹 이벤트 = open, close, buttonClick 등
- 특정 페이지에서만 뜨는 모달, 여러 페이지에서 뜨는 모달이 있는데 이를 분리해서 관리하는 게 나을지, 하나의 방법으로 통일하는게 나을지에 대한 고려 필요.
- 여러 개의 모달/바텀시트를 띄울 수 있어야 한다.
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
- https://overlay-kit.slash.page/introduction.html
- https://velog.io/@rjw0907/useOverlay-2.-%EC%BD%94%EB%93%9C-%EB%B6%84%EC%84%9D%ED%95%98%EA%B8%B0-TossSlash
- 상단 방식과 유사하나 하단 차이점이 존재한다.
- 차이점
- close(UI상 닫기), exit(unmount용) 분리되어 있음
- ➝ 모달/바텀시트가 꺼질 때 애니메이션 주는 것이 가능
- Map객체를 사용해 index로 키를 주지 않음.
ContextAPI 사용 시 단점: 페이지 리렌더링
리렌더링을 최소화하는 방법으로 observer pattern이 존재한다.
3안) observer pattern
https://velog.io/@minsu8834/Observer-pattern-with-React-%EC%A0%84%EC%97%AD-Toast-Modal2%EB%B6%80-%EB%AA%A8%EB%8B%AC
<Observer pattern with React 전역 Toast, Modal(2부 모달)>
참고 사항 해당 글은 지난 시리즈()를 바탕으로 기술되오니, 안 보신 분들께서는 참조 후, 읽어 주시기 바랍니다. 1. intro 지난 시간에, 1부에서는 observer pattern을 활용해 리랜더링을 방지하면서도
velog.io
해당 방법을 사용하면 리렌더링을 줄일 수 있지만, 익숙하지 않은 class형으로 작성해야 하므로 빠른 개발과는 조금 거리가 생길 수 있다는 단점이 존재한다.
그 외 논의사항
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