pile·
프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

항공 프론트엔드 구축기 (5/10): 뒤로가기가 가장 어려웠다

여기어때 항공 프론트엔드 팀이 웹·웹뷰 환경에서 뒤로가기를 구현하면서 겪은 난관과 3세대에 걸친 진화 과정을 다룬다. 안드로이드 하드웨어 백키 제어권을 앱이 웹에 위임하는 구조에서 라우터 기반 접근이 모달 스택을 인식하지 못한다는 문제를 발견하고, 결국 모달 자신이 뒤로가기를 처리하는 방식으로 설계를 전환했다.

핵심 포인트
  • 라우터는 모달 스택을 볼 수 없어 뒤로가기 시 페이지 자체를 이동시키는 문제가 있었다. 해결책은 "라우터가 아니라 모달 자신이 뒤로가기를 처리한다"이다.
  • 웹뷰에서는 DOM data-order 속성으로 순번을 부여하고 전역 함수가 최댓값(가장 나중 마운트)을 선택한다. React 상태 대신 DOM을 직접 쓰는 이유는 앱의 호출 진입점이 React 트리 바깥이기 때문이다.
  • 웹에서는 URL 해시 #modal을 모듈 싱글톤이 단독 소유하고, 모달은 자기 등록만 하는 3세대 설계로 해시 충돌 문제를 해결했다.
  • 모달은 "닫힐 것"과 "버틸 것"으로 분리하고, 닫힐 모달에 onHistoryBack이 없으면 컴파일 에러가 난다.
  • 웹과 웹뷰의 남은 차이 하나(겹친 모달을 한 번에 닫을지 하나씩 닫을지)는 구조적으로 감출 수 없어 한 줄 분기로 노출했다.
상세 정리
  • 문제 상황: 예약 취소 화면 위 바텀시트 위에 다이얼로그가 열린 상태에서 안드로이드 백키를 누르면 다이얼로그만 닫혀야 하는데, 라우터는 이 모달 스택 구조를 알지 못했다.
  • 웹뷰 구현: 모달이 마운트될 때 보이지 않는 버튼에 data-order로 순번을 새기고, 앱이 호출하는 전역 함수가 data-order 최댓값의 버튼을 클릭한다. React 상태를 쓰면 트리 바깥에서 접근이 어려워 DOM을 직접 사용했다.
  • 웹 1세대: 라우터 안에서 뒤로가기 처리 시도 → 모달 스택을 모르므로 페이지 이동 발생, 실패.
  • 웹 2세대: 모달이 열릴 때 URL에 #modal 해시를 push하고 닫힐 때 history.back() 호출 → 오랫동안 잘 동작했으나 해시가 하나뿐인데 모달 주체가 여럿이라 충돌 발생.
  • 웹 3세대: 모듈 싱글톤이 #modal 해시와 popstate 리스너를 단독 소유하고, 모달들은 자기 등록만 한다. popstate 리스너도 단 하나.
  • 엣지케이스 처리: 닫히자마자 다시 열리는 경우, 전역 로딩 후 남는 엔트리(페이지 소속 기억으로 해결), 코드 스플리팅으로 인한 언마운트/재마운트, history.back() 후 뒤가 없어 popstate 미발생하는 경우, 백키 연타로 두 칸 이동하는 문제, 모달 두 개가 동시에 사라질 때 배경 노출 등을 각각 처리했다.
  • 모달 타입 분리: "닫힐 것"은 onHistoryBack 핸들러 필수(없으면 컴파일 에러), "버틸 것"(확인 다이얼로그·로딩)은 뒤로가기를 무시한다.
  • 남은 차이: 웹에서는 #modal 해시가 하나라 겹친 모달이 한 번에 닫히고, 웹뷰는 data-order 순서대로 하나씩 닫힌다. 이 차이는 구조적으로 감출 수 없어 한 줄 분기로 명시했다.
왜 읽나웹과 웹뷰를 동시에 지원하는 환경에서 모달 스택 뒤로가기를 설계하려는 프론트엔드 개발자에게 DOM 기반 접근과 URL 해시 싱글톤 패턴의 구체적인 구현과 엣지케이스 처리를 보여준다.
여기어때 (GC컴퍼니)
여기어때 (GC컴퍼니) 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2