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

항공 프론트엔드 구축기 (6/10): 컨테이너를 둘로 쪼개지 않았다

여기어때 항공 프론트엔드 팀이 웹·웹뷰·데스크톱 세 환경을 단일 코드베이스로 운영하면서 레이아웃 컨테이너를 환경별로 분기하지 않고 하나로 유지한 설계 결정과 그 구현 세부를 다룬다. 핵심 질문을 "이 컨테이너가 환경을 어떻게 분기할까"에서 "환경을 몰라도 되게 하려면 무엇을 밖으로 내보내야 할까"로 전환한 것이 설계의 출발점이었다.

핵심 포인트
  • 컨테이너가 받는 prop은 mode(전체화면/카드), header, bottomBar 셋뿐으로 환경 분기 로직이 전혀 없다.
  • 환경별 관심사는 뒤로가기·오버레이·하단 여백 각자 전담 훅/컴포넌트로 분산시켜 컨테이너가 전체 그림을 알 필요 없게 했다.
  • 헤더 그림자는 스크롤 위치 > 0 여부만으로 결정되며, 화면 코드에는 관련 코드 한 줄도 없다.
  • 아이폰 홈 인디케이터 여백은 훅 안에서 화면 회전마다 재계산하고 컨테이너는 숫자 하나만 받아 여백을 생성한다.
  • 하단 버튼 높이를 모르는 초기 상태의 CLS 문제는 높이를 재기 전까지 버튼을 화면 하단에 고정하는 방식으로 해결했다.
상세 정리
  • 배경: 항공권 예약 서비스를 웹·웹뷰·데스크톱 세 환경에서 단일 코드로 운영하는데, 레이아웃 컨테이너(헤더+본문+하단 버튼)가 환경 차이를 모두 알아야 하는 구조가 문제였다.
  • 질문 전환: "어떻게 분기할까" 대신 "무엇을 밖으로 내보내야 컨테이너가 환경을 몰라도 될까"로 설계 관점을 바꿨다.
  • prop 최소화: 컨테이너가 받는 prop을 mode·header·bottomBar 셋으로 줄이고, 나머지 관심사(뒤로가기·오버레이·하단 여백)는 각자 전담 구현체로 분산했다.
  • 헤더 그림자: 스크롤 컨테이너 내부의 스크롤 위치가 0을 넘으면 그림자를 켜는 로직을 훅이 단독 소유하며, 화면 코드에는 관련 코드가 전혀 없다.
  • 아이폰 홈 인디케이터 여백: CSS env(safe-area-inset-bottom)를 화면 회전 시마다 훅 안에서 다시 읽고, 컨테이너는 숫자 하나만 받아 padding-bottom을 생성한다.
  • 하단 버튼 CLS 방지: 초기 렌더 시 버튼 높이를 모르면 레이아웃 시프트 발생 → 측정 전까지는 position: fixed로 화면 아래에 고정하고 측정 완료 후 정상 흐름으로 전환했다.
  • 헤더 타이틀 말줄임: 좌우 아이콘 유무에 따라 타이틀 최대 폭을 동적으로 계산해 아이콘 영역을 침범하지 않도록 했다.
  • 스크롤 제어 분리: useLayoutContainerActions(액션)와 useLayoutContainerState(상태)로 훅을 둘로 나눠 "맨 위로" 버튼이 스크롤마다 불필요하게 리렌더되지 않게 최적화했다.
  • 결론: 환경 차이를 한곳에 모으지 않고 흩어 놓아 각자 흡수하게 하면 아무도 전체를 알 필요가 없어진다는 원칙이 컨테이너 설계 전반을 관통한다.
왜 읽나웹과 웹뷰를 동시에 지원하는 레이아웃 컨테이너를 설계하면서 환경 분기 코드가 곳곳에 퍼지는 문제를 겪은 프론트엔드 개발자에게 관심사 분리의 구체적인 적용 사례를 제공한다.
여기어때 (GC컴퍼니)
여기어때 (GC컴퍼니) 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

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

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

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

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