Toast 컴포넌트 재개발 및 동작 구조 개선

Droomii·2024년 4월 4일
0

이 문서는 ToastMessage를 새로운 디자인에 맞춰 재개발 하면서, 기존에 있던 ToastMessage의 문제점 파악 및 보완 과정을 정리한 문서입니다.


Store 명칭 변경

기존에 사용하던 토스트 메시지 Store의 명은 CommonMessageStore였는데, 코드를 처음 보는 사람은 정확히 어떤 형태의 Message인지 바로 알아볼 수 없는 문제가 있었습니다.

그래서 이번에 ToastMessage를 재개발 함과 동시에 ToastMessageStore라고 명칭을 변경하였습니다.

기존 ToastMessage 동작 구조의 한계

  1. showMessagetrue일 때, 즉 현재 출력중인 토스트 메시지가 있을 경우, this.hideToastMessage() 메서드가 호출되는데, 기존에 떠있던 토스트 메시지가 트랜지션 없이 바로 사라지면서 연출이 부자연스러워지는 문제가 생깁니다.
  2. 토스트 출력 여부가 showMessage에 의존하게 되면서, 3번과 같은 문제가 생깁니다.
  3. 기존의 ToastMessage의 올라오고 내려오는 모션은 전부 css animation keyframe을 통하여 이루어집니다. 즉 State의 변화에 따라 메시지가 올라오고 내려오는 것이 아니여서, setTimeout을 통해 억지로 타이밍을 맞춰 메시지를 hide 처리 및 destroy해야 하는 불편함이 있었습니다. 더 나아가, ①번에서 timeout을 정리해주지 않기 때문에, 원래 떠있던 message의 timeout이 먼저 발동되면서 토스트가 더 일찍 hide/destroy 되는 문제가 있었습니다. (아래 gif 참고)

구조 보완

  • 외부에서 Toast 컴포넌트를 참조하기 위해, ToastRefType 이라는 타입을 새로 만들었습니다.
  • useImperativeHandle Hook을 사용해서, 컴포넌트 외부에서 Toast 내부 State를 변경할 수 있도록 했습니다.
  • durationMillisec 속성을 통해 isVisible 상태를 해당 시간에 맞춰 변경해줍니다.
    • isVisible 값을 판별하여 관련된 className을 추가해줌으로써 Toast가 위로 올라오는 트랜지션을 활성화시킵니다.
    • Toast 컴포넌트를 공유하는 Snackbar의 경우 노출 시간이 무제한이므로, timeout을 설정해주지 않습니다.
  • 정해진 시간보다 일찍 Toast를 닫아야 하는 경우, 초반에 설정했던 timeout을 정리해줌으로써 부자연스러운 연출을 방지합니다.

Toast 컴포넌트의 interface입니다.
• 해당 메시지를 출력해주고 있는 Toast 컴포넌트를 연결해주기 위해 ref 속성을 정의합니다.


새로 만든 ToastMessageStore입니다.

  • ToastMessage를 List로 저장합니다. 기존 Message와 신규 Message의 Cross Fade를 연출하기 위함입니다.
  • deep: false 로 설정하면 MobX는 class 속성들의 참조 객체의 변화만 관찰하게 됩니다. 즉 messageList 내부의 요소들은 관찰하지 않고, 완전히 새로운 list 객체가 할당되는 것만 관찰합니다.
  • 앞서 ToastMessage의 인터페이스에 ref 속성을 정의했습니다. 새로운 메시지를 출력하고자 showToastMessage 를 호출했을 때, 기존에 존재하던 모든 ToastMessage에 closeToast 명령을 내려 강제로 닫히게 한 후 새로운 Message를 messageList 에 추가합니다.
  • messageId 는 렌더링 시 key를 부여하기 위함입니다.
    • List 객체가 변경되더라도 key는 동일하기 때문에, React가 해당 key를 참조하여 re-rendering을 방지할 수 있습니다.

위처럼 작성한 토스트 출력용 컴포넌트를 앱 최상단인 _app.tsx에 추가해주었습니다.

  • messageList의 항목들로 Toast 컴포넌트를 순차적으로 렌더링 해주는 동시에, 각 항목의 ref 속성에 해당 컴포넌트의 ref를 할당합니다.

    • Toast 컴포넌트 ref를 message의 ref에 할당해야 ToastMessageStorecloseAllToastMessages 메서드가 정상적으로 동작합니다.
    • makeAutoObservabledeep: false 를 전달하지 않을 경우, ref의 변화를 계속 감지하게 되어 무한 재귀에 걸리게 됩니다.
  • Toast 컴포넌트가 완전히 사라졌을 때, ToastMessageStoreremoveMessage 를 통해 데이터를 정리해줍니다.

개선된 ToastMessage는 위와 같이, 기존에 출력중이던 메시지가 있는 상황에서 새로운 메시지가 출력되는 경우, 이전 메시지가 자연스럽게 Fade-out 되면서 새 메시지가 Fade-in 되며, timeout 설정 충돌로 인해 부자연스럽게 갑자기 사라지는 문제도 해결되었습니다.


ToastMessage 컴포넌트는 프로젝트에 단 하나만!!

기존에 사용되던 ToastMessage 컴포넌트(현재는 ToastMessageDeprecated로 이름 변경)는 CommonMessageStore를 거쳐서 _app.tsx에 있는 Global한 ToastMessage를 통해 출력되는 경우와, 별도의 컴포넌트 내에서 ToastMessage 컴포넌트를 독립적으로 사용하여 출력하는 경우가 공존했었습니다.

이 부분에 대한 일관성이 떨어진다고 판단하여, 모든 ToastMessage는 ToastMessageStore를 통해서만 출력하는 방식으로 통일했습니다.


보일러플레이트 제거 - showUnknownErrorMessage

알 수 없는 오류에 대한 Toast 메시지 출력을 자주 사용하는데, 이를 출력하기 위해서 매우 긴 코드를 매번 작성해야 했습니다.

대략 160군데에서 Occurence를 찾았는데, 긴 코드에 대한 피로감, 복사/붙여넣기로 인해 과도하게 생기는 중복 코드를 해소할 필요성을 느꼈습니다.

알 수 없는 오류 토스트 메시지의 특징:

  • 문구가 동일하다.
  • 버튼으로 닫을 수 있다.
  • 하단 margin이 다른 경우가 있다.

위 특징을 토대로, ToastMessageStore에 오류 메시지만을 위한 별도의 method를 하나 추가했습니다.

showUnknownErrorMessage({ closable, bottomOffset }: Pick<IToastMessageProps, 'closable' | 'bottomOffset'> = {}) {
  this.showToastMessage({
    closable: closable ?? true,
    bottomOffset,
    icon: 'FAILED',
    text: TOAST_MESSAGE['UNKNOWN_ERROR'],
  });
}
  • 버튼으로 닫을 수 있는 것을 기본으로 하되, 만약을 대비하여 버튼으로 닫지 못하게 만드는 ‘closable’ 옵션을 추가했습니다.
  • bottomOffset을 통해 하단 마진을 조정할 수 있습니다.

코드의 양이 확연히 줄어듦으로써 코드 작성의 피로감도 많이 줄일 수 있을 것으로 기대됩니다.

profile
What, How 이전에 Why를 고민하는 개발자입니다.

0개의 댓글

관련 채용 정보